Compare commits

...

59 Commits

Author SHA1 Message Date
74acbd9a5a Update docs/DEDICATED_SERVER.md 2023-12-01 08:22:48 -05:00
25d1ec1789 Add build flow to Dockerfile, rename Dockerfile to Dockerfile.server, Add sample compose.yml, tweak DEDICATED_SERVER.md to reflect new reality 2023-12-01 13:16:33 +00:00
97b46ad01a Fix AppImage updating, which has apparently been broken for *years*. Why didn't anyone tell me???? 2023-11-25 18:22:43 -05:00
17d11647e7 Fix CI (Hopefully) 2023-11-25 02:39:19 -05:00
48137f9665 Final Tweaks 2023-11-24 22:16:13 -05:00
c956151603 CI Fixes 2023-11-24 18:53:16 -05:00
73b51be43f Try Fancy CI 2023-11-24 18:37:08 -05:00
71b11b314b Animated Chests 2023-11-24 17:57:39 -05:00
168b825bf4 Clean Up CI 2023-11-24 12:09:21 -05:00
edca3ad394 Fix ARM Build 2023-11-24 03:43:15 -05:00
1aed33a48b Improve Flatpak Build [skip ci] 2023-11-24 03:28:17 -05:00
177c5262a6 2.5.2 2023-11-24 03:07:06 -05:00
7969fb2127 3D Chest Model 2023-11-23 23:54:36 -05:00
b0aac13057 Ȩ̴̛̛̛̝͉̬͙͕̯̬̙̥̬̺̫̭͉̱͈̭͕͍͈̲̯̀̔͒̑̈͛̂̃̑̓̄̊͌͆̎̉͐́̀̇͒̋̒́͊̈́͊̓̈̓̍̑͛̑̃͒̌͗̐̈́̈̈̒̏̀́͌̿̂͂̽́̂̉̅̽̀͌́̔̔̾̃͆̓͗͊͌̃͘̚̚̕̚̕͝͠͝͝ê̴̢̨̧̢̧̛̛͖̲̠̞͓̪͕͙̖̟̯͕̭̘͖̤͕̲̬͔͎̞͇̩̦̳̲̺͎͍̱̯͔̼͙̯͙̮͔̼͎̫͇̯͚̳̭̇̂̃̃̓̅͐́̆̀͑̅͌̓͑̏̄̆͊̅̀̒́̌̄̌̈́͆̋̔͐͊̓͒̈́̋̉͆̑͑̿̈͑̓̾͗̿̈̏͘̕͝͠͠ͅͅḩ̵̛̘̣͖̤̪͉̦͓͖̈́̎́̌̂̈͌̍̍͋͒̊͊́̑̓̓͘͘͘̚͜͝͝͝†̷̧̨̨̨̩̩̺͔̖̼̺͙̰̞͚̻̙̙̜͙̯̪͉̻͈̲͎͔͉̟̻̱̦̝̱̝͍̩̣̞͍̘̥̪̺̺̜̯̹̬̮̱͚̥̦̲̙̪̮̠̩̞̫̞͙̰̪̤͖̙̖̼̖͇͚̆̋̋̀̃͒̑̌͛̍̿̄̏͜͠ ̵̢̛̲͈̩̭͙͙̺̠͇̲̣̗̗͕͍͚̼̯̣̮̌̾̉̓͐̄͐̓̇̂̔͂̔̒̈́͌̏̓̔̾͆̈́͌̑̄͋̃̎͑͛̈́̃̋͑͑̍̍̀̂͆͛͂̇͒͂̃͐͊̈́̍̆͆̐͊̈́̓͑́̐̾͐̈́̒̔̓̾̈́̄̔̚͘̚̕̚͘͝͠͠ͅñ̸̨̢̡̳̙͔̗͕̙̩̝̖̹̪̺̟̼̝͓̬̻͓̮̣̜̖̤̯͖͕̗̝͖̩̬͎̳͚͙̤͙̞̺͉͇̱̩̪̞̭͙̱̫̯͚͓͌̋̄̃̔́̐̀̐̍̈́̽̑͋̋̉̽̌̅̈̅̓̇̓̈̚͘͘͜͝͝͝ð̷̡̧͈̭̤̙̦͇̺̝͖͖͈̤̦̤̗̐̅̓̓̓͐͒̐͗̓̃̈̿̾̒̈̈́̂̇̂̅̽̈̈́͊́͛̅̾̔̀̎̈̽̒̓̎̎̂̀̏̽̃̌̎̎̏́̒̓͗͐̾̍͒̓̏̽̚̕̕̕̕͝þ̴̨̢͕̙̯͈͉͈̳͔̪̥̲̯̥̞̜̪̬̺͖͈͉̼̟̠̥͈̮̻̼̤̤͎̘̪̻̗͐̉͌́̎͑̊́̇̈́͑͐͆̋̆̾͂̈̉̑̈̊́̾̈́̈͂̀͛͂̂͊̓̂̆̉̀͌̒͊͊́͆͛̿͑͌͗̏̽̎̋̚̚̚͘̕͜͜͝͝͠͝͝͝͝͝ͅµ̴̡̨̛̤̭͇̫̹̳͇̞̼̳̣̙̘̫͇͖͈͕̮̀̉̒̎̈̀̓͒̽͊̎͋̆́͂͒̎̉̎̒̑͆͌̒̂̎̂͋̇͌̄͛̑̏́͂̾̎͂̅͋͗̄́̿͌̔͌͘͘ ̶̧̧̧̮̩̜̝͈͙͖̼̦͕̥̻̬̣̜̣̣͉͙͕͔͓̟͚̩͎͍̣̟͔͚͚̼̦̥̯̠͎̩͎̣͈̦̯̰͎̦̜͚̀̇̄̎̀͑́͆̽̓̋̀̎̾̃̅̏͌͑̇̏͗̅́̽̈́͐̑͊͂̃́͐̈͐̀̍̽̔͗̊̓̓̾̉̄͗̈́̓͗̇̈́͛͗͒͑̏̈́̿͗̂̀͒̾̽́̀͒̓̔́̕̕̕͘͘͘͝͠͠͝͝͠͠͝ͅͅͅͅL̵̢̡̧̧̢̨̨̨̛͉̺̖͍̻͉̟̯͔͚͎͚̝̻̞̠͓̻̰͉̲̺̦̤͍̫͙̲̺̣̬̜͖̮̬̪̣͙̩̮̭̪͖͖̱̺͚͍̳̙͚̣̬̥̫̳͔̬͇͙̣̍̓̆͊͐̂̀̔̾͌̔͐͂̃̍̍̊̈̈̐̌̈́̔͂̎͊̅̏̍̽̚̚͜͝͝͝͝ͅͅl̴̢̧̢̩͍̩̫̙̤̦͇̟̪̩̙̠̮͖̩͇̘̼̫̼̲̣̟̩̘̟̳͇̜̠͔̗͈̯͙̞͓͛́̍͂́͑͛̇͂͌͆̇͂̾̐͑̒̌́̒̑̒̈́̃̉́͑̈́͋̓̒͒̽̉̔̆̈́͑͆̍͒̂͆͊̚̚͘͘͝å̶̡̢̢̢̡̨̨̡̢̛̮̰͚͕̹̘̼͈͖̠̲̗̰̥͈̪̲̱͕̦̘͎̖͇̗̜͇̬̤̞̱̯̟͎̲̦̩̜̳̮̟͈͇̫͎̯̗͙͔̼̒̊̐͊̍̂̈͑̑̎͊̒͑̊͋̽̂̉͜͜͝ͅͅͅ¢̶̡̧̧̛̜̲͕͇̮͙̹͎̘̦̖̺͕̪͔͈͉̝͓̖͖̫͖̥̌͛̅̀͆̎́̈́͒̃͑̋͘̕ͅ ̴̨̨̛̛̛̛̛̛̰͈͔̪̥̲̲̮͖̗̺͕͚͚̦̞͕̩̙̲̫̘͚̙͚̖̳̙͈̮͉̬̹̯̞̥͉̼̦̣̠̠̹͚̗̫̝̤̩͓̪̜͒̆͗̀̐̊̔̿̈͆̍͌̾̀̓̅͑͑̄́͂̓̋͆̇͆͆͑̎̑̆̽́̒͊͂̄͊̅͗̅͌͋͑͑̎̈́́̈́̆̿̏̈́̍̊̍́̄͋̀̉̿͛̏̌̉̊͌̒̌̑̌̚͘̚̕͜͜͠͝͠Ì̷̡̧̧̛̻̟̻̤̗̙̻̻̠̖̰̞͍̤̥̙̳̮͉̱͉̳͚̉͒̎̎͌̓̈́̐̓̅̍̈́̍̂̀͒̾͑̈̚͜͠,̴̡̢̨̡̨̡̧̧̛̰͉̫͎̼͍̬̠̬̳̠̫̳̼̻̜̞̪͔̖͓͇͉͖̥̩͚̫͇̱̤́̃͆̄̒͆̒͌̉̄̓̂̂̎̂̌̇̓̈́͌́̃̌̓̈́̋͐́̀͂̓̒̀̄͗̒͂̾̂̈́̍̌̎͂͌̏̊͘͘̕̕͠͝͝͠͠͝ ̷̡̨̛͍̗̙̹̻̳͖͉̟͇͖̙̖̹̙̰̩̘̗͉̗͇͙͔̝̗̯̫̭̬̰̩͉̥͖̪̤̮̟̖̯͖͔̫̦̰̞͍̹̠͔͇̠̣͉͙̖̙̪͔̪͎̍͐̌̀̎͑́̉̈́̐͗̋̅̄͗̈́̉͆̿̓̈́̑̓͐̋̈̇̽̑̈́̓͐͛͐̓̏͒̈́͋͂̎́͐̈́͌̏̓̎̉̾̚̚͘͝͝ͅͅͅÌ̵̢̧̧̨̢̛̛̛̼̙̩̖͓̫̥͎̳͖̟͍̙̦̟͔̲̯̱̮̣̻͙̪̹̭̞̻̠̠̟͔̭̘͔̝̯̮̳̺̗̪͇͇̳̼̦͎̺̩͈̑́͋̌̋̄͌̇̍̓͂̋̆̎̔͌̀͋͆̏̈́́̍̇̈́̿͐͊̅̇̒͐̾̽̌̇́̅̑̒̄͌̿́̈̏͌̂͌̽̿̌̽͛̏͗̋̾̍̈̈́̽͐̏̒̇̈́̂̃̚͘̕̚̚͜͠͝͝͠͝͝͠þ̸̧̧̨̢̨̛̱̲̜͍̤̺̭̮̫̯̼͕͈͍̮͎̥̫̖̙͙͍̞̱̳̜͕̙̯͈̯̣̬̹̰̘̤̣̫̯̙̲͔̝̲͇͙̖̗̬̪̭̱̯̙͈̰͓̱̲̯̈̑̿͌͜͜ͅͅÇ̸̧̢̨̡̨̨̡̛̛̛̛̛̛̲̜͎̙̖̞̪̝͉̹̣̙͙̠̣͈̭̥̺̰͇̤̰̹͔͍̹̥̱̬̫̠̙͖̱̙̞̮̘̟͇̥̹̺̺̝͇̲̣͖̭̭̥͋́̃̓̉̈́̒̂̏̀̉́̾́͆͛̐̂̏́́̇͂̀̏̇̆̓̓͑͐͊̾̾͌̂͑̀͛̏͐̂͒̏̎͆̋͒͛̐͊̃͋̇̈́̓̈́̈̆̚̚̕͜͜͜͜͠͠͝͝͝M̶̡̢̧̨̡̢̛̲̥̯͉̗̥̙̲̬̫͚̼̜̝̲̲͓̖̬͔̖̦͕̫̗͈̼̹̥̣̙͚̮̣̘̟͕̰͍̬̘̮̱̲̘̜̫͚̹̤̘̥̲̮̦̱̲͍͔̝̤̣̣͕̯͕͍̞̘̰̲̮͗̎̓̀́̀̾͐́͋̍̐̽̂͌͑̅̒̏̾̇̋̓̋̈́̈́̐̅͌̈́̀̿̏̈́̒͊̃̽̐̎̆̽̄̄́̊͗̉͋̅͗͒̔̀̐́̈́͌͗̑̀̑̋̍́͂̓̏̈́̐̓̚̕͘͜͝͠͝͝͝͝ͅͅ ̸̧̢̛̛̗̣̺̩̤̬͓͈̦̙̠͉͕̲͚͕͉̩̺̬̣̳͇͍̞͈̬̉̏̔͊̔͂̋̌͒̔̎̊͂͆̓͆̄͌̓̊̈́̐̀̋̅͗͋͊͌͑̿͋̊̈͒̅́͌͒͒́̋̈́̂͌͂̇͒̈̽̔͛̉̔̀͘̚̚͝͠†̷̨̨̡̡̡̨̢̡̥͖̫͕̭͕̣̜̹̺͍̤̞̩̤̮̙̟̞̝̱͇͖͕̗̪̺̝̳͈̘̬̰͉̳̭̼̗̝̞͚̫͕̺̬̪͎̤͉̣̱̩̜́̂͂̽͑̀̀̃̇̽͑̾̈́͑͐̂͝͝͝ͅͅḁ̶̢̡̢̨̧̭̳̺̼̪͔̻̯̗͎̠̪̥̟̙͔̬̳̤͎̪͍̥̳͈̤̱̩̦͇͈̳̻͉̻̥̺͉͓̣̟̝̺̱̣̰̙̱̹̞̱̣̘͍̜̩̥̳̥̭̻̠̥̬͚̤̫̩̮̯̜͍̊̌̀͊́͂̓̒̀̈́̎͑̌́͆̒̇̽̂̿̋̓͊͂̑̌͂͛̿̊͌̈́̈̅̎͋͂͐͑̏̊̑̉͒̎͑̃̎̾̌̈̀̅͐̿̎̒̄̐̌̃̿̈́̎̉̿͋̑̌̂̑́̌̂̇͛̾̕̕̚̚͜͠͝͝͝ȩ̸̢̛̲͖̳̦̖̝͈͉̬̟͚̲͍͚̬̲̥̳͙̰͖̺̙̥̜͈̹̪͚̗̰̹͍̘͓͙̞̬̤̫̞͉̤̲͔̘̭̻̯͍̂͑͊̇̾̀͐̐́̅̒̋̃̑̅͋̀̌͒̆̇́̾̾͂̀̒́̍̒̄̏̓͗̄͑̓̏́̇͒͑͊͑̾̐͗̉͆̅̓̔͗̾̍̏͆͂̾̅͊̽̆̄͌̕̚̚͜͝͝͠͝͠͠͝ͅͅͅͅr̴̢̡̨̡̡̢̧̡̢̨̛̛̛̗͙͖̱̖̣̣̰̻̘̮̻͉̳̙͈̳͚͓̟̦͚͕͓͈̬̥͚̪̰̘̗̮͖̯̬̯̲̠͎͍͔̯̩̱͕̲̺̖̥͈̼͍̤̗͕̻̗̘̙̥̦̼͔̘̰̹̱̰̫̜͇͎͚̞̥̝̉͂̈͐̃̒̔̑̈́̉̒̃̄̀̈́̃̔͆̄̓͋͐̓̍̊̉̍̈́́̒̓̍͛͌̾̎͘͜͜͝͝͝ͅͅģ̶̧̡̧̧̨̧̡̡̨̛̛̹̻̥̱͕̥͙̮̣̻̖̭̪͚͍͖̬̲̠̞̭͈̼̙̼̼̺͍͚̟̭̫̯̦̬̭̤̙̺̪̹̣̦̘̭̠̞͕̳̻͙̟̼͉̘̞̥̭̪̺͔̼̠͖̫̥̬̦̳͈̱̱̳̀͊̅͛̎̆̓̑͒̉̀́̎̔̇̈̂̓̓̏̈̔̇͛̏̑̊͐̈́̀͊̎̏̆̿͗̂̓̊͂͒̌̄̄̔̈̔̏̔͊͊̏̂̅̄͒̀̇̚̚͜͝͝͝͝͝ ̷̢̧̡̢̢̧̺̗̙͉̠̺̤̝̲͔̠̹̬̬͎̪̖̜̘̪̪͚̹̜̥̖̻̙̩̝͕̥͚̜̣̞̫͎͚̪̰̣̭̻̠̲̮͙͙͈̜̣̲̫̺͖̤͉͙̤̻̲͖̙͗͜͜͜͜H̵̨̧̡̡̨̧̛̛̛̛̛̫͈͔̲̭̠̮̪͕̳̼̞̞͈̪̲̺̤̗͓͙̠̬̪͈̩͇͈͎̤̼̳̣̰͉̯̻̋͂̌͂̋͒̂̎̂̑͂̑͐̌̈́̌͑́͐̀̆̏̎̀͌͗̅̉̇̾̆̋̂͒̋̈́̾̓̏͌̈̏͐̈́̇̓̅́̓́͗͋̀̓͑̿̑̅̀̎͊͋̌̀͆͛̕͘̚̕̚͜͝͠͝͝͝ð̶̨̧̨̧̛͇̟͔̮͉̹̻̘͔̞͇̬̹͚̞̗͓̘̠̺̹͚̝̳̗͚͚̝͈̣̠͖̦͙̰̹͍̬̘͙͚͙̺̘̩̙̰̖͇̤̺̎́͛̍̓̊̒̈́̈́̋̈́̋̋̓̾͋̈́͐̈̓͋͋̄̑̒͛̑̄̉̅͋̽̓̋͑̉́̋͑͗̽̂͊͆̊̉̀͑̍̊̑̏̒̄́̆̊̆͒͌̒̾̌̓̐͛̄́̆̇̿̅̃͗̈̄̈́̄̓̎͐͘̚͘̚͘͘͘̕͠͝͝͠͝ͅͅ!̸̢͇̺̲͙̣͚̮͙̝͖͚̒̽̌͂̓́̌̒̓̋̓͐͑̐͋̐̀̔̊̍̂̎̍̆̓̅̒͋̆̇͂̑̀̔͊̏̕͘͝ 2023-11-13 17:45:05 -05:00
80c0f24a5d J̶̨̡̨̨̧̧̧̧̨̢̢̢̧̡̨̢̨̢̢̧̧̢̡̡̧̨̤̩̱̗̣̗̱̮͇͇̻͕͉͇̗̭͕̻̝̼̻̤̞͍̫̣͔̗̠̠̪͕̮̙̤͓̯̦̙̬̫͙̖̙͙̬̜̗̙̬̩͔̹̱̠̩͎̞̪͖̯̹̲͙̥͈͕̼̜̪̙͔̖͈̞̣̝̦̤̺̺͓̮͓̮̝̤͙̗͉̗̞̟͈̞̼͕̩̻̥͖̤͇̪̪͍̗̥̼̲̝͇̜̥͚͍͕̞̝̖͔̜̦̮̫͍̜̬̥̲̮̜͈̠͙̭̱͇̥͈͇̩͖̝̱̪̹̜͕͈̫̦̭͉̞̪̤̗͙͚̰̙̝͚̹̝̫̪̤͇̠̮̤̜̭̰̞͚̝̫̭͉͕̠̻̤͕̫̳͓̳̯͍̣̭̘̤͚̯̖͓̼͕̥̞̘̤̣͔̥̮͉͍̝̣͇̳͇͎͈͖̤͎̯̬̬̭̗̫͚̹̫͉̮͋̕͜͜ͅͅͅu̶̢̡̢̧̧̡̡̨̡̧̢̧̨̢̢̧̧͚̟̥̘̭͇͈̳͔̠͓͔͉̱͙͓̪̱͚͉̬͎͉͚̹̩̪̥̤͍̖̜̼̦͍͎̫͇͉͔͔̙̪̝̱̥̭̠̭͈̙̗͓̲͙̰̻̲̬̪͖̤̹̘̬̻̜͙̫͇͎͚̺̩̬̬̰̬̣̲̯͓̝̝̹͖̼͓̹̺̯̱͚̗̩̱̠̠̺͎̮̮̪̤̝͇͚̫̹̼̗̺̻̗͓̠͕̯̯̱̘͎̳̱͉̙̪͇͓̼̯̙̘̪̲̳̭̝̹̦̮̳̗͔͈̪̰͇̖̥͕͓̺͇͔̝̲̫͉̣̭̥̹̼͖̺̰̳̪̠͕͉͚͎̪̪͎̪̩͍͛̊̈́̾͒̈̂͐̑̿͑̀̾̎̋͗̒͋̄̑͐͗́̈́̎̆̉͛̎̋̐͗͒̄̑͌͆̀̉̐̓͐̆͂̂̿̓̇̽͒̀̀̍̓͒͛͆̇̂͒́̄̔͛̎́̓͆̈͐͒͛̽̕͘͘̚̚̕͘̕͘͜͜͜͝͠͠͝͝͠͝͠͠͠ͅͅͅͅͅͅͅͅͅs̷̡̧̨̡̢̢̨̧̨̧̢̢̡̨̡̧̢̨̢̡̨̢̢̨̧̡̛̛̛̛̛̛̛̥̹̳̬͚̠̼̲̬̞̺̤͈̪̲͓̣̤̬̘̰̮̪̟͍̪̗̦̲̺͉̦̤̟̖̲̲̠͉̙̜͇̞͓̻̣̞͉̝̪̺̰͓̝̥͙̘͎̘̭͎̺̮̼̥̩͓̘̠̺̪͚̝̯͈͚̬̦̯̦͕͖̰̭̠͇̣̙͉̻̘̜̟̤͔̳̣̖̻͚͚̻̺̦̳̣̘̣̩͉͈̳͎̫̯͕̪̠͕̲͚̜̗̗̫̫̱͎̯̙̣̗̫̭͎̩̫̟͚̦̟̙̤͉̜͔͙̙͙̫̣̮̱͙̳̺̺̗̼̻͍̣͍͙̯̝̼͕̭̦͖̻̝͍̝̪̮͇̯͔̼̣̩͕͕̘̭͕̖͓̭̉͒̂̌̐̂͑̋͂͋̈͐̈́̄̂̅͗͋̾̓͌̈̀̇̂̇̃̉̎́̀̏̐̐̽̿̏̈́͗͑̓̇̊̊̇̓̈́̒̀̅̈͗̓̌̄͂̽͆̇̓̄̿̃̀͛͊̽̉̑̉̑̆̅̽̎͒̔̔̒̍͆̑̐̽̄͛̐̀́̓̈́͋̍̿́͂͊̃͐͐͋̊̌̒̾͗̓͊͐̋́̔̍́̐̑̌̏̆̓͋̐͗̀͋̆̓̃̇͂͗̎̅̈́͒͗͗̔̆̈̃͋̆̒̇̄͗̋̓̔͛̉̈́̑̂̉̐̋̎̅̅̆͒̈̀͂̾̂̐̂̊͋͋̔̃͋͆̑̇̂̾̆̎̈́̉̍̽͊͊̏̎̏͐͋͂́̾̀̓̽̐̀̓̉͂̅̂̋̅̇̚̚͘̕̕͘̚̚̚͘͘̕͜͜͜͜͜͜͜͜͜͜͝͝͝͠͝͝͝͠͝͠͠͝͝ͅͅͅt̵̡̧̛̛̛̛̛͙͉͍̱̰̘͍̻̬̝̦͎̣̖̙̠̬̻̝͔̣̲̩͍̱̑͒̍͊̊̎́̊̂̂̄̽̿̀̍͌̏̽̿́̍̆͆͗̔̈́́͊͂̆̾̿̐̓̈́̓͑̑̋͗̀͐͛̐͐́́̒͂̆̒̉͌̇̄̀̃̆̂͛̽͐͌̓͗̾̔̊͐̎͐̄͌͋̐̅͋͋͒̋̊̊̀͂̇͛̅̂̐̀͒̒̉̇̃̒̔̔̍͌̒̌̌͊͐͂̑͒̾̽͊̈́͑̐̈̑̉̽̇̆̓͂̋͌́̿̓̇̀̏̾̈́̏̌̔̉̐̽̃̿̈̈̉̈͌͐̊́̾̊̔̉͆̄͆̈́̈́̍̈̃̽̊̉͌̆͑͆̊̽̽̒̒̿̏͋́̀̅̍̏̑̈̿̋̃̈̄̏̋̋̈́̎͆̍͛̀̈̂̀̀̀̈́̐͑͌͑͒́̀̎͛̒̍͗̊̉̒͋͋͒͐̌̏̆̑̓̈́̾̒͂̆͌͆̀͊̐̎̂̅̾̋̔̂̀̍̏̓́̇̽̾̅̚̚̚̚̕̚̚̕̕͘̕̚̚͘̕͘͘̚̕͘̚̚̚͜͜͝͝͝͝͝͠͝͝͝͠͠͝͠͠͠͝͝͝ͅ ̸̡̧̡̡̧̧̧̧̡̨̡̨̨̧̧̢̛̛̛̛̛̛̘̰͓̼͍̗̥̮͕̝̞̟͇̞̙̻͙̯͕̗̺̠̺̗̭̰̗̺̗̲̥̯͎̫̻͔͓̩̜̦̘͓̬̳̟̻͈͔̯̩͕̙̩̝̬͇̣͓͇̱̹̼̹̹̩̰̣̹͈͈̬̜̳̪̹̠͓̥͎̩̥̥̳̱̩̹̝̘͕̗̘̲͓̺̱̹̫̱̟̜̺̹̥͎̳̺̹̘̞̼̟̪͉̝̼̣̤͓̮̩͍̜͍͖̻̺͈̤̹̙͇͙̭̙̜̰̻͙̣͕̼̘̠͓͉̲̻̟̻̦̺͕͍̦̩͖̰̥̩̱̱͚̩̹̰̹̙̫̟̣̗̝̹̝͉̻͉̟̗̙̫͖͈͖̬̜̬̞͍̭̻̤̣̪̭͍̗̠̬̘͕̠͈̖̥̓̀̈̉́̆̌̅̈͊̊̔͗̀͆͒̓͗̽͋̉̒̈́̃͛̾̄̄̀̍̑̀͗͋͛̓̈́̓̈̓̊̅̔̀̓̾̄̈̓̂͊͛́̾̉̉̎̍̆̅͆̀̇̈́͆̌̔̓̈́͌̄̈́̋͌̍̅̓̈͐́̉̀̓̊̍̽͗̂͑̐̔̅͆̀̎̓͊̽̔͑̽̂̓̇̓͐̎͑͛̽̄̈́̉̾͒̍̑̀̐̓̀̇̿̈́̎̊͂͑̇̎͆̇̄͒͋̀̿̓̐͆̈́̂̿̏͂̍̈͌̏̆̿͋̈͂́͗̎͊͒̅̈́̋̃̉̽̀̿̎̈̅̇̽̒́̐̈́̎̑̄̂͗͛̌̄͊̀̓͋͘̕̕͘̚͘͘̕̕̚̕̕̚͘̚͜͜͜͜͜͝͝͠͝͠͠͝͠͝͠͝͝͝͝͠͠͝͝͝͠͠͝͝͠͝͝ͅͅͅͅͅͅͅF̷̢̨̧̨̡̧̢̛̛̛̛͓͈̱̯̬̭͖̫̞̻͖̲̭̝͕̤̤̜̬̖̺̹͎̺͚͎͙̮̟̖̗̻͚͕̙̦̱͚̝̯̜̙̩͈̱̙͖̞̞̰̳͕̦̬̙̉̏̈́̑̎͛͑̆̆̽̉͂̏̑̅̌̿̔͛̿͌̏̿͋̈̐̾̃̈́̾̃̃̀͒̈́͋̅̇̐͒͆̿̄͋̉̋̏́̅͊͑̆̔̽͌͗̒̀̽̍͗́͋̔͆̎͊̊͌͑̾̈̂̑̿͆͛̅̒̇̈́̆́̉͊̆͐̀̋̊͛̌͌̈́͑̿̓̐̈́̅͌̑̅̒͋̒̈́̈́̃̈́̃̂̍̐̏̾͒̀̉̌̀́̂͐̌̒̇̉̅͗̓̈́̽̍̇̂̉̅̿̒̓̾͂̾̿̈́̂̔͋̆̈́̈͌̃̍̈́̎̈̑͆̅̓͗͆͋̈́̂̀́̏͑̃̂̍̐̃͆̓̕̕̕̕̕̕͘̕͘̕̕͘͘͜͜͜͜͝͠͝͝͝͝͝͠͝͝͝͝͝ͅͅͅi̵̢̧̢̨̢̨̧̛̛̛̥̣͕̤̤̥̺̪̝͖̫̪̻̠̲̹̞̟͖̲͕͓̝̲͉͇̙̻̞̤̩̟̣͈̜̞͈̠͇͈͍̜͔̙̦͚͔̣̙̪͙̩̝̮͇̪͙͉͖̪͈͖͖̳̗̯̠͉̭̋͗̄̾̂͆̆̐͛͗́͆͌̃̉̂̋̓̇͑̈́̓̊̀̂͆̒̾͛͗̉̌̍̅̑̀͊͑͛̐͋͆̽̎̑̂̎͑̑̌̓́̈́̒̂͌͛͐̐̿̽̈́̐̀́͆̐̌͊͊́͒̌̂͑̏̔̎͂̍̏͑̇̍̑͊͊̀̂͂̈́̎̅̔̍̅̑̅̽̃͑͒͗̿̑̔̒̑͋͂̂̑̍̄̇͒̐̌̽̀͊̔̊͋͒̾̂̿̏́̀̃̇͐̔̈́́̊̽̄̉̅̓̈̾̀̓̄͂͛̂́̍̉͆͂̓̀̂̓̎̉̎̔̓̐̂̂́̌̕̚̕̕̚̚͘͘͘̚̕̚̚͘͘̚͜͜͝͝͝͝͝͝͝͝͝͝͠ͅͅͅx̶̢̨̢̧̧̧̨̧̧̢̧̧̨̛̛̛̛̛̛̮͎̜̳̻͕̥̰͇̺̝̤͖̬̲͓̫̗͇͎̻̯͍͙̺͉̱͔̤̫̻͚̣̹̥̰̼̫̱̫͖̱̙͓̭̞͇̪̯̘̠̯̩͙͕̙̼̣̪̻̻̥̹͍̹̭̫͓̱̻̖̮̹̮̥̥͕̲͉̝͈̥̗̪̥͚͚͚͕̺̠̹͔̫̤̰̘̯͕̳͈̭͖̣͖̹͖̩̼͙̯̭̣͈̣͇͈̘̫̖̠̞͍̬̫̲̳̻̹̱̳̞͙͔̠̱͕̮̯͉̩̪͎̯̪̻̜͒̇̋̂̋̽͗̔́̐̂̂̾͆̑͗̏̐̈́̃͐̊͌̓̓̐̌̈̇̽̋͛̐͋̄̏͑͋̿̿͆̈́̐̾̈́͗͌́͑́͗̍͒̌̒̅̇͌̽̊̈́̾̒͒͒͆̅̾͌̑͛͑͒́̍̉̏̊́͂̌̇̔͛̾̾̓̽̑̌̾̏̔͋̾̈̊̑̎͋̈́̄̾̄̅͋͐̿̆̋̈̄̐͊̆͋̋̽̂̌́͆͆̎̇̄̃͊̈́̉̔̍̓͆͑̃̋͂̀̉̏̉̓͊̽́͊̓̈́͑̂̑̎̉́̈̈̋̂̈́̒̽̓̑̐́̇͒̊̿̎̔̑͊̿̈̀̈̅̈́̑̊͒̎̏̄͆̈́̇̄́̈́̑̓͗̒̃͒̀̈́̅́̑͑̂̃̂̿̆͌̎͆͌͋̅͑͋͊̈́̓̾̂̏̀̾̔̅̐́̏̓̈͊͋̇͐̅̽͌̂̀̓̏̒̿͌̿͗͗̎̾̀̈́̓̈́̊̚̚͘̚̚̚̚̚͘̕̚̚̚͘̚͜͜͜͜͜͜͜͠͝͝͝͠͝͝͝͠͝͠͝͝͠͝͝͠͝ͅͅͅͅͅͅͅͅ ̸̡̨̧̢̧̢̢̨̧̢̡̢̧̧̨̡̧̨̛̪̜̼̟̤͚̯͓̪̦̥͎̯͕̟͍͚̼̩̩̤̖̥̤̤̳̻͕̼̣͍̜͙̙̭͙̗͇̣͎̦̭͔͙̥̯̬̭̯̞̼͓̳͙͉̲͍͚̲̺͕͕̟̼̻͇͔̺̼͙̦̣̝̖͍̻͍͎̣̹̻̯̲̗̝̤̞̗͍̭̮̮͕̣̗͋̅́̍̍͌̉̊̃̓̓̂̈́͐̓̇̉̋͋̅̊̔͆͗̅̑̓̀̂̍̆̈́̈́̉̃̃̾̏̀͗́̒̌̈͌̅̿̏͆͒̌̀͛̓̐̋̾̀̈́́̾̇̈́̿̀̈́̊́́͛̽̀͌̔̈͆̉͌͋̑̌̈́̽̈́̉̀͛̓͋͗̚͘̚̚͜͜͜͠͝͝͝͠͝͠͠ͅT̴̢̢̡̢̨̡̧̡̢̧̧̡̡̡̨̨̛̛̛̛̛͖̻̯̼̗̫͕̥̝̦̹̪̙̖̩̯̮̦̗̤͚͙͙̬̣̙͈̥͓̬̰̗̤̞͖̞̪̣͎̬͈̹͕͔̦̤̙̝̣̗̜̘͎͙͕͇̟̟̠̮̹̪͖̟͎̼̯̻̮̭͕̬̖͖̼̺̟̠̫̳̖̜̳̖͕̫͔̺̼̯̫̯̫̘̙͔̰͍͎̜̭͎̖͎͚̬̘͔̯̞̝̞̜̣̥̼̙͔̤̦̭̲͍̝̩̼̦̣͇̘̟̦̳͚͔͔̟̣̼̺͎̮̟̖̠̞̗͍̪̯̠̱̭̬̰͍͓̝̲̯̙̭̯̤͕͓̲̹̣̼̦̳̝̞̰͔͍͂̿̓̔͗͌̋̆̓̋̈́̽͑͒̓̈̎̇͗͂̒͐̅̽͗͂̂̇̓̀̍̈́͗̐͂̇̇̽͑̾̏̊̑̓̋̒̒̿͐̐̿̓̅͋̑̋͛͋͗͌̌̑͂̐̏̇̽̑͛̓͗̓̃̂̈́͛̈́̆̌̆̓̋̆̏̊̾́͊̀̐͛̿̅̃̑͑̔̀͂̾̇͑̿̎̐͌͌͌̿̈̽̃́̃̆̆̈̋͑͊̍̈͛͊̐̐̀̍́͊̿̂̌͗͑̃̐̍̊̐̑̈́͛̔̏́̋̈́̑̔͆̎͋̏̒͗̍͗̋͑͂͑̐̑̏̿̃̅͆͒̽̈́͋̋̀̈́̽̑͗̂̿̑̓͐́̊͋̾̄͑̂͐͗̓͛͆̀̏̈́̊́̈́̍̈́̽̂̊̈́̆͊͗̽̈́̓͊̂̅́͗͌́̾̑̀̊͋̀͘̚̚͘̕̚͘̕̚̕̕̕̚̚͘̕͘̚̚͘͜͜͜͜͜͜͜͝͠͠͠͠͝͠͝͠͝͝͝͝͝͠͠͝͝ͅͅͅͅͅͅḧ̵̢̨̨̡̢̡̡̢̡̨̧̡̨̡̢̧̧̨̨̧̢̨̛̛̛̛̛̛̹̞̥͇̘̰̭͖͙͖̥̙̗̹̘͔̞̬̟̠͓͔̪͇̗͈̠̭̱̘̘͎̲̭̼̯̺̘̤̖̲̜̪͔̻̘͕̞͙̜̻͙̦̞̰͉͓̖̞̖͇̺̗̜̩̩̪̺̣̹̺̥͇̘͍͎̫̦͇̻͓̲̗̼͖̙͍̪̙͉̬͓̝̹̳͈̠͎̤̥̯̩̬͙̫̮͕̦͕̻̫̞̙̲͈̤͉͙̤͔̰̗̜̲̹͔̫͈̜̲̦̮͎̗͔̥̤̣̯͈̲͇̝͎͔̺̙̗̲̫̭̟̓̏͋̎̑̂̈́̀̄̉̾͋̍̒̑́̐̈͊͗͊̾̾͛̽̎̊͒̑̈́̀̑͌̌͗̑͋̍̔̎̍͋͗͂́̌̆̏͊͌͒͐͊̈́́͋̈́̒̅͒́̓̉̓́͌̐̓̎̀͂̄̉͌͂̍̌̒́̌͌̃̀̇̂̒̓́́́̃̾̉́̒̆̈̒͒̌͛͋̋̂̒̈͊͐̀͐̾͐̄͋͌̈̐̓͑̍̅͋́̀͂͛͐̉̿͒͌̈́̅̈́̆̉͊̑͊̈́͒͐̌̆́̈́̔͐͋́͐́̆͌͂͂͗̀̋͊̎̏̑̑͛͋̈́̌̿̾̂͋́̅͂̌͌̅́̄͌̎̇͋̾̀̓̀̑̚̕͘̕͘̕̕͘̕̕͜͜͜͠͝͝͝͝͝͝͝͝͝͠͝͠͝͝͠͠͠͠͝͝ͅͅͅͅͅȩ̸̢̢̡̧̨̡̡̨̨̡̛̛̛̛̛̩̪̫̲̣̹̦̝̠̺̬͖̥̬̝̞̥̦̣̤͍̘͈͉̤̘̬͉̖̫̼̘̬͕̗̲̞̟͈͖̥͖̞͔̞̣̘̙̙͖̱̮̙͉̬̤͎̲̙̦̲̱̹͓͉͕̞̰̳̤̰͍̠̻͕̥̺͙̹͇̳͈̻͌́́̀̋͂͒͗̉̏̌̆̒͒͆͒͋͌̀͒͆̂̔́̈̈́͂̾͂̓͗̓͒̆͋̏͛͒̐̈́̋̐̈́͛̉́͆̅͆̾̂̉̄͊̓͆̊͑͐̔̒͂̒̏̆̀̌̉̓̃̔̈̓͛͗̍́̀̉̅͌̀͋͋̒̿̍̒̍̈́̆̐̐͋͆̉̆͒͑̉̅͂̓̿͛́̌͊̍̊̽̽́̓̈́̿̍͑̔́̑̆͗̎͛̅̐̿̑͆͌̉̓̊̑̈́̋́̎̇͂͒̑̇̾͆͘̚͘̕̕̕̕͘͝͠͠͝͝͝ͅͅͅ ̴̡̡̨̨̧̧̢̡̧̡̡̢̡̡̢̡̨̧̧̡̢̛̛̛̛͔̝̦͉̜̭̹̫͕̻̫̳̩̰̖̤̮̮̬̱̖̱͕͙̲̹̞͎͚̻͎͎̜̗̻̪̥̥͈̖͖̖̗͎̖̼̺͖̫̪͙̻͚̱͙͚̤̞̮̯͉̲̰̼̥̙͔̱̹̭̗͙͖̥̥͕̙͎̖̮̼̤̻̙̟̹̣̞̼̫̗̳͇̖̮͍̪̹̞̦̙̹̮̭̫͍̭̹̯̗͍̣͕̱̭͓̩̗̭͍̰̱̦̮̤͓̲̮̞̖̘̲͚̗̜̲̯̱̠͍̠̺̰̳̙͎͉͉̟̮̲̩̖̤͙̰̖̙̤̹͙̭͎̹̺͙̼̠̻͉̯̪̪̫̳̰͇̘̣̻̪̙̗̠̝̤̩̤̣͚̦̹͖͚͖̜̳̦̝̺̳̻̼̩̹͖̥̞̗̥̩̱͐̓̂̊́̄̃̈́̏͋̿̿͂̐̎̈́͊̈̌͒̇͐͊̆̽̅̔̓̎̃̀͗̾̓͋͆̃̌̀̾̋̽̇͛̿̀͒̄́̉́̀̉͛̅̎̀̽͋͑͋͗̊̀͋̾͛̈́̇̐̓̏̄̂̅͘͘̚̚͘̕̕͘͜͠͠͠͝͠͝͝͝ͅͅB̴̨̧̨̢̡̨̨̨̡̨̧̡̡̢̢̡̡̛̛̲͕̹̠̮̠͖͇̮̩̰͎̱͕̳͈̱̫̹̮̗̠̙͓̮̖̻̫̟̣̗͕̫͇̗̥͔͔̩̳̥͙͔͙̥̻͙͉̣̯̮̰̩̹̼͎͓̜͈̜̖̰̣̹͕͚͔̜̹̭̗̖͍̪͔̙̭͖̹̹̮̜̮̤̜͔̜̺͕̮̯̦̣̱̖͍̭͎̦̣͎͔͚̙̩͇͓̫͎̼̻͖̼̮͖͓̼͈̞̘̤͇͈̺̞̗͔̩̮̪̱̠̬̼̺̬̲̤̖͕͍̣̤̦̘̻̹̠̘͖̥̜̮̤̰̬̖͈̤̀̈́̉̇̑̇̈̄̑̀͊̓̈́͌̾̀͊̾̀͆͑́̂͛̋̓̅͌̆̓̌̇̈͒̾̾̽̇̈̅͛̐͊̓̉̌͂̂̅͗̆͛̀̋͋̂̌͛̈͌͊͆̀͐̊̑͒̃͂̌̾̏̂͛̉͌̉̄̈́̄̀̈́̉̌͋̈́̅̀̐̀̾̈́̓̽͑̃̏͒̄̓̍̌́̀̿̐̿͒̈́̌̃̏̈́̏̈́̅͗̂̾́̆͌̏͒́̈́́̑̾̆͒̄̓̈́́̈́̀̇̒̒͋̇̈̔́̀̽̎͌̎̌̊̚̚̚͘̚̚̚̕̕͘͜͜͜͜͝͠͝͝͝͝͠͠͝͠͠͠͝͠͝͠͠͠ͅͅͅͅư̵̢̧̡̨̡̢̡̧̨̢̧̡̡̨̡̛̛̛̛̛̭̮͕̳̜̬̠̟͎͎̘̜̹̣̥̦͎̰̺̼̹̣͕̰̖͔̣̳͉͙͚̫͎̹̫͍̟̫̺͈̫͔̺̗̬̖̳̖̯͖̲̤̦̙̹̠̬̭̺̥̲̦͇̳͖̤͖͉̮͉͇̟͍͔̟͍͕͍͕̙̱̘̜̺̲̠̞̹̟͚̦͈̘̦̼̠̟̳̳̝͎̟̟̣̮̙͚̱̱̝͈̭͍̟͐̑͋̍̂̆͂̓̽̈́́̈́̑̅͊̔͑͐̓͋͆̄̅̔͒̇̋̒̓́̏̀̉̒̀̿̐̈́͗̃̏̅̾͊͗̎̏̀͆̄̈́̇̈̔̔̏̇́̀̈́̒͐̑̏̾̌̐̉̒̏̏͌̄̒̇̍̃̊̏̅̓̃͐͗̓̽̌͌͌̌̓̑̓́̂̽̓͛̂̀͌̾̄͆̀̅̌̒̆͗̐̊͆͒̀̔͆̔̏̀͒̍͋̀̅͛̒̂͂̅̉͋̿̿͌͛̍͗͑́̀̃̀̏͋̃̓̿̆̈́͘̚͘̕͘͘͘̕̚͜͜͠͝͠͝͝͝͝͝͠͝͝͠ͅͅͅį̵̛̛̛̲̫̯̟͔͖̟͇̦̠͓̘̪̻͋̾̀̿̌̀̑̿̍̈́͋̒̔̄̑̏̄̏̒͐͋̏̍̏̋̈́̃́̇̋̀̃̆̈́͒̒̅͑̈́͑̈̾̌̀̒̾̈́̊͂̅͋̃̽̃̑͋̄̍̎̑̃͒̐͂̋̊͒̌̋͌̈́̂̇̋̑͆̄͒̀͑̿͐͗̈͑̈̇̓̇̈́͋̌̄̉̂̀̀̈́̀͂̆̆͗̋̄̅̈́̔́̀̂̾̇̊͗̿͊̀̋̾̎͐̑̉͛̉̓̾̐͌̊̆̍͆̈́̆͊͂́͊̒̀͆́̌̋̾͛̈́̾̍͋̍̉͛̽̽͆̊̀̄͗̃͒͆̐̓̈͐̔̓̅̓͌̋̕̚̚̕͘̕̚̕͘̚̚͘͜͝͝͝͝͠͝͠͝͝͝͠͝͝͝͠͝͠͝͝l̸̢̨̨̧̢̢̨̡̧̧̨̢̡̢̡̡̡̢̡̡̡̨̨̛̛̛̝̰̜͎̹͉͔͓̹̱̞̥̦̘̯̩̣͈̤̙̘͈̗͖̺͇̳͙͔̩̯̱͍̱̝͖̮̭̼̬̬̙̘̜̼͉̺͔͔͇̜͖̞͔̱̱̱̘̝̦̻̘͚̤̤͈̞̟̟͚̠̯̪͖͉͖̪͔̻̖͍͖̘̟͖̳̦̻͖̮͍̬̥͚̼͔̩̯̳̯̗͚̭͚̤̩͖͍̱̻̮̦͈̪̱̹͇̬͕͉̬̦̝̜̳̣͉̘̑͐͐̅́̾͌̄͋̋̀̊͒̓̅̑̿͑͗͌̃̈́́̑̔̀̑͆͛̆̐͛͊͆̍̐͋̔̋͋̈͗̃̃̓͊̀̆͆͒̀̑̒̃́̅̆̒̈́̽̈́̒̽͌̐̌́̓̽̾͑̓̅̂̇̐̊̈́̏̾̾̀̉͒̃̾͛͐͊͆̇͌̅̈́̔͛͐̄̈̊̓̋̈́̾͑̈́̀̆̎͑́͊̀̎̐͌̋̋̅͒̒͆͋́́̈́̏̔̾͋͂̄̕̚̕̚͜͜͜͜͝͝͝͝͝͠͠͝͠͠ͅͅd̴̢̢̡̛̛̛̛̛̛̛̛̪͔̖͇͚͓̩̪͔̜͓̣͍̫̳̺̳͇̖͖̟͉̹̟̪̫̖̜̩͙̗͕̳̹̥̭̩̙̹͔͙̟͔̥̰̙̆̂̈́̍̔̌̈́̃̿̒̏̓͐͆̎̎́͗̈̽͌̆̆̑͂͛̃̆̈́͗̇͐̿̈́͒̽̌͗̾̌̐̓̈́̽͗̾̇̑̔̂́̓̓͛͋̏́̏̑̓̍̑̓̃̂̀̀̏̆͋̉̇̀͂͗̄͐̊̈̓͊̈́̽͑́̽̅̽̓̑̈́̂̈́͋́͗̑̇͋̒̋̎͛̈́̿̃̔̐͂̍̀̃̔̀̏͂̅͋̆͆̇͒̈́̾̽͂̎͊̀̄͛́́͋̉͗̓͒͌̈́̏͑̆͒̏̓̃̓͐̏̈́̋͛́́̂̈́̓̋̐̅̏͗̓̓̅͋̔́͋̏͑̑̀̓͐͒͌̀͛̔̐̓̈́̉̈̈́̍̋̄̈́͋̇̆͑̂͗̾̂̒̉̍̈̐̐͑͑̂̾͋̀̐͆̔̌̆̍̽̅͆̓͋̀̽̔̈̽̀̃͑̉̂̈̄͆̌͗̃̎̈́̇̓̍̈́̌̃̋̚̚̕̕̕̕̚̕̕̕͘̕͘̕̕̚̚̚̕̕͘͜͠͝͝͝͝͝͠͝͝͝͠͝͝͝͠͠͝͝͝͠͠͝ͅ ̵̡̨̨̧̨̧̧̨̛̛̛̪͍͖̜̟̫̬͚̦̮̪͚͓͈͙͕̺̮̬͍̯̼̞̤̙̬͚͚̖̳̹̙̤̦͙̖̼̜̫̟̘̺͖͖̰̗̦͔̫̟̗̞̹͇̣͕̫͇͔̪̬̯̩̥̭̗̫̤͉̖̻̳͔͎̤̪̻̜̦̬̫̩̟͚̺̼̩͎̖͚̩̫͖̰͐͋͑̎̎́̊͒̈́͗͆͗͑̀͌̐̎͂̑̏̾̈̈́̈́̃͌́̄̈̿̍̊͊̉̇̀̾͌͐̐̄̐̂̈́̏̒̈̀̀̎̊͋͐̈̏̅̾̈̎̆̂̍̅̈̀͌̒͌̈́̇͑̇͌͂̅̈́͐́̇͌̓͊͑̃͐̓̉̈́̊̒̀̎̔̾̉̌̓̊͌̓͋͒̏̒̀̈́͒́̓̂̂̌̄̈́̀̊̐̈́͂̓̈́͊͛̃̾̈̿̆̿̍̔̈́̽͌̊̅͆̎̊͑̉́͛̌͑̏̒̈́̈̍̔̿̊̀̂̊̾̌̃́̈̈́̐̃͆̽̒̅͊̓̿́͋̀͊́̂̍̿̐̀͋̀̂̈͑̄̍̔͌́̆͑̑̎̌̊̈́͊̿̈́́̂̇̉̋̆̌̕͘͘͘͘̚͘͘̕̚̚̕̕͜͜͜͝͠͝͠͝͝͠͝͝͝͝͠ͅͅ(̴̢̧̡̧̛̘̭̗̥̙͉̮̯͉̦̯̦̫̙̹͚̜̜̥͙̰̯͇̖̩̼̲̙͚̣͍̳̲̬͖̜̼͙̤͓̫̟̺̻̹̲̖̗̺̥̫̘͓̻̗͎̪͎̖̩̩̦̦͈̣̣̺͉̺̥͎̠̯̦͚̝͈̬̲̭̻͍͚̲̯̍̈́̈͊̾͗̊̀̓͌͌̒̎́̒̎́̅̓̅̅̈́͆̃̄̊̊́͂͒̈́̒̉̓̓̾͗̑̔̐̈́̇͗͐̈́́̅̈̄̓̓̎́̓̂̋͆̈́̂̑̑̽̈́͆͛̒̅̑̊̋̔́̎͂̈̔͋̈́̃̀̑̒̍̆̌̎͋̀͒͒͊̈̂͐̈́̽̀͌̆͐͌̈́͆͂̆̐̓͂̈̌̀́͐̑̅̋̈͐̽̂̐̇̏̐͐̄̅̔̚͘̚̚̚͘̚̕͜͜͜͝͝͝͝͠͝͠͠͝ͅͅH̵̡̢̡̧̛̛͖̤͈̣̺͉̳͈̖̜̘͖̪̼̣͇̮̜́̆̐͌̓͗̾̇̈́̏̎̊̋͑̍̌̒̉̐̐̄̓͂̽̐̅̑̀̅͗̅̀̓̓͐̅̌̉͛̀̐̓̈́̔͌̉͌̽̉͆̿̊͊̓̈́͋̓̀̇̓̂̒̓̉̀̆̊͛͆̽̽͐͐͛̇́́̀̐̓̄̾̉̎͋̑̿̐͋́́͐̿̈́̍͋͂͑̀̒͛͋̐́̿͛̓̋̏̓̓̋̎̽̉̔̈́̓̒͋͐̄͗̈̊̀̇̑͋͒̾͊̈̄́̽͘̕̕͘̕̕͘̚̚̕̚͘̚͜͠͝͝͝͠͝ͅǫ̵̡̢̨̧̧̧̨̨̧̨̧̨̡̧̧̧̨̡̧̡̨̨̡̛̛̛̛̫͇͓̗̠͈̬̼̖̗̤͕͈̠̙̼͕̪̮̜̠̻̲̺̯̣͎͙̥̯̖̳̤̞̮̞͕̭̮͓̻͕͖̝͈̟̰̱̪̤̣̰̤͉̠̰͚͇̼̯̫͍̣͚͎̭͙͍̱̳̪͈̞͈̹͚̲̞͔̙̪̞̗͈̩̹͇̠͇̩̲̟̳̖̻͍̤̜̙̜̥̪͕̤͔̮̤̗̜̪̺̺̙͕̜̞̘̳̪̹̰̺̠̩̻̤̤͍̦͚͖̩̟̖̻͕͇͕͇̲̘͎͈̪̬̜̻̜͓͓͚̮̠͚̠̼̳͎̖̜̣̘̙̹͓̮̗͕̟̭̘͉̥̩̳̱͚̣̣̱̗̩̭̦̗̦̞̦̱͙͍̰̟̱̩̠͖̮̟̱͖̰͈̖̺̣̮̗͇̦̣̬͓̳͙̰͉̖̹͖̜̫̭̥̗̦͍̘̫̯̯̗̼̩̮̟͔͖͍̞̽̅̓́̈̀̂͛̈̑̂͋̒̆̌͂̀̎̋̋͌̌́̀̌͊͊̏͋̃́̋́̈̎̎̒̌́̒͋̾̈́͊̃̃́̍͛̄̾̐̆̅͐͗͒́͋͛̌̅́͌̀́͒̇̔͂̌͗̐͛̽̀̀͗̾̂͂̃̈̐͋̒͐̐͗͗̍̿̏̀̈̾́͌̔͌̃̅̄̀̈̂̍͒̈͊̂̾́̈̋̈́͌̐͊̈̓́̒̓́̍͊̾̾̂͂̍́͌͛̆͐̓̈́́̽́̂̂͗͛͌͆̀̍̑̊̿̋̏͛͗̍̀̆͆̀̐̈́̀̒̎̐́̽͊̃̏͊͊̋͒͆̈̅̀͒̕̚̕̕͘̚̕͘͘̕̕̕͜͜͜͜͜͜͜͜͝͠͝͝͠͝͝͝͝͝͝͝͝͠͠͝͠͠ͅͅͅͅͅp̵̧̢̧̢̨̛̛̛̝̻̗̳̝̜͈̖͎̠̩̳̝̺͕̫̯͖̱͙̦̦͚͕̘̖̺̠̠̩͙̜̪͇̼̺̖̆̎̾̽̑̉̽͐͒͌͌͐̀́̿͗͊̒̇̿̇͛͂̀̊͆̈́̂́̒͑͛͂̇̃̑̉̈́͊̈́̌͌̽́́̃͊̂̓̓̐̽̊͌̈̈́͌͑͛̍͂̏͑̎̉̀̍̾͒͂̉̉͊̇͌̓̈́̀̅̇́̿͌̈́̉̂͌͌̐͑̐̐́̔̽̋̀̔͗̽͑̏̊̔̈̀͗̈̓̒̍̏͑̒͑̉̆̊̈́̈́̈́̈́̈́̓̈́̎̾̈́̂̆̊̆̏̀̄͐͒̉̈̈́̚̚̕͘͘̚̚̕͘͠͠͝͠͝͝͠ͅͅę̷̧̡̢̨̢̨̧̢̢̡̛̛̛̛̛͚̩̙̠͇͕̲͇̗͕̤͓̘͚̬̣͖̞͖̬̪͕̼̪͈̺̙̼̳̩̰̪̥̭̼̠̩͓̗͉͖̦͎̮̱̰̺̙̯̯̺̣̲͙͉̘͍͇͕̻͉͇͕̪̥̳̦̼̜̥̟̖͚͖̼͎̦̺̖̲̯͍͓̯̣̪͓̝͉̦̗̬̦̩̜͎͉̗̗͍̜͓͚̲̤̩̖̳͖͔̜̝̰͕̹̻̤̖̮͎͎̱͉̮̗͕̙̞̘͎̰͎̣̗̼̬̻͓̺̬̟̬͓̥͉̱̖̤̣͕̰̱́̾̎̉͆̆̀͛̈́̉͐̅̆̋̾̌̋́̅̊͂̀͒̓̿̆͊͐̈́̐́̎̃̔͆̈́̒̒̋̓̓̐̽̔̌̈͒͒̐̀͊̇͂͂̈̓͊̽̎̅̐̈̔̒̌̅͑̒͗̔́͛̃̈́̊͛̈́̾̅̓͑͗͐̀̌̇̈̾͌̌̄̃̈́̐͗̅̈́̈́̾̈́̓̊̃̿̆̀͐̒͆̃̂̀͗̈̈̍̿͒̉̉̒̅̇̅̄͌͂̀͋̑͂̍̊̔̌͌̈́̓̄̋̔̃̍̿͑̉̿̾̍̄͛̈̀̇̐́̅̈́͆̎̈́̂̽̂̑̏̃̏͂͌́͌́̿̀̒̂́̅́̓͂̂̈́͋̏̇́̈́̄̽̉͛̂̽̀̈̍̀̀̐̉͌̇̑̓̆̇̍̄̐̊́̅̊̈́̿̀̍̑́̇̾͒͌͂̎͆͐͗̆̇̇̄̅̀͘͘̚̚̚͘̕̚̕̕̚̚̕̚͘̚̕̚͘̚͜͜͜͜͝͝͠͠͝͠͠͝͝͝͝͠͝͝͠͠ͅͅͅͅͅf̸̢̡̡̨̢̢̢̡̧̢̢̡̢̢̨̡̢̢̢̨̢̧̧̨̛̛̛̛̛̤̭͖͇̯͍̣̜̖̟̭̭͍̰͙̯̥̱͕̤͕͇̯̩̫͈̯͚̗͇̘̦̠̦̻̟̗̳̩̥̜̱͓̬̺̦̭̥͕̖̪̻͖̳̭̮̠̘̜͈͙̤͉͚̬̞̲̳͖̦̯̞͚̜̘̯̭̥̹̯͕̱̞͙̝̳̗̰͓͙̲͇̹͍͚̣̺̮̖̤͇͉̫͉͙͇̺̦̳͉̟̫̘̹̦̮͈̟͕̫͚̤̫̱̙͍̫̲͎̪̙͔̣̮̺͉̳̫̪͇̹̜̹̺͍̟̜̼̩͇̮͍͙͙̫͕̳̣͖̬̟͓̙̗̟̪͖͕̜̰̳̼̱͈̳̩͓͙͍̖̼͔͔̬̼͓͖̯͉͖͎̻̘͙̤̯͇̠̗͈̦̈́́̐͂̀̐̆̌̇̂̊̑̅̈͐̆͗̒̑͋̅͊͂͛̀́͐̌̍͐͆̋́̏̍̓̒̎̎̍̉͂͐̐͗̀͒͆̈́͆͋̈̍̐̆̿̔̉́̀̾̃͂͗͑̋̈́̽͆͋̽̔̔͛̈̊̿̔͗̂͛͗̉̈́̉̀͊̓̒̆̔́̓̐̎̀̂͌̎̈́̀̓̀͒̈́͑͒̿̏̑͒̉̍͗̓̌̋̋͗̈̿̇̐̈́̇̍̂̓̊́̊̈́̈́̒͋̈́͆͋̽̔̓̅͂̇̽͑̃̅̀̓͗̌̐͛̓̊̀̉͛̽̅͒̌̀́̽̋̄̈̏̽̕̕̕̚̚̚̕͘̕̕͘̕͘̕͘͜͜͜͜͜͜͜͠͠͝͝͝͠͝͠͝͠͝͝͝͝͠͝͝͝͝͝ͅͅͅͅͅͅͅͅͅͅų̷̡̨̡̡̡̫̣̜̬̮͇͖̙͎͖̣͙̻̹̼̭̣̜͉̥̩͚̩̲̼̼̩̣̳͓̣͎̩̼͔̬̤̣̩̘̤̱̟̭̘̙̪̫̞͕̘̤̩̥̹̣̱̾̌͂͊̋̃̒͗́͊̈͑̒͋͘͜͠ͅͅl̸̢̢̨̡̢̡̧̡̛̛̛̛̛̛̛̛̥̯̳̬̥̺͙̰͎̭̞̘̫̼͉̫̜͖͕͔͚͇̯̮̪͖͓̥̺̦̟̪̠̣̻̣̗͖̻̲͚̮͕͈̹̺̣̮̣̘̠̜̻̘̬̹̘̠̼̳̬͕͍͇͉̲̞̪̦̰͚̘̙̺̫̻͎̦̟̹̣̠͍̭̗̖̤̫̣̣̞̱̦͇̫͚͚͇͖̯̬̤͕̪͇͈̳̠̣̻͍͖̼̹̱͕̹̟̀͂̎̈́̑͋͋̇͊͛͑̎̈̐͌͆̐̊̀̅̂̾̄͛̐̽̔́̏̀̈́̿͆̇͊̓̎̈̎̎͐̈̾̌̄̐̃̋̈̇̅͗̑̊̈́̽̌̃̈́̽̋̊̆̅̽̍̔̇̉̒̊͂́̇̃̒͆͊̌̓͌̅͊̄͆̅̂̈́͆̄̈̀̊͗̎̉͌̿͛́̂̌̊̎̎͒͊͛͗̍̀̋́̇̎͛̈́͂͐̂̆͒̈̆̑͐̾͗̆̊͐͊́̓̔͐̅̋̐̈͆͐̓̆̑̑̌̄͋̿̀͋͋̽̄͒̾͌̅͛͊̒͌͂̎̃͌̇̃̿͆̉̇͂̀͆͗̎̊͌́̀͗̽̚͘̚͘̕͘̚̕̚̚̕̚̚̕̕̕̕̚͜͜͜͠͝͝͝͝͠͝͝͠͠͝͝͠͠͝ͅͅl̶̡̧̧̧̧̡̡̨̨̨̢̢̢̡̧̨̢̧̨̡̨̡̢̛̜̳̩̪͈̰̣̤͓̙͔̗̣̗̹͉̞͉̠͖̪̭̗̲̰͇̠̱̯̟͖̯̝̠̳͍̬̳̬͔̠̟͍̬͓̜̪̭͚̟̝̺̫̩͈̩̼͕̘̜͔̹͇͙͍̪̪͔͇̩̠̟̲̝͕̖̥̣̣͕̮̣̤̙̥̼̫͓͍̥̘̩͍̯̺͍̪̠̮̟͔̠̟̮̣̞̻̥̠̯̣̯̮̬̘̦̖̝̩̥̝̪̫̱͍̥̺͙̺̹̱̤͇͕̜̻̮̞̫͖̠̳͚̪̘̖̟̭͔͙͈̜̟̰͖̪͚̖̤͕͓̘̮͚̼̰̜̺̪͖̥̠̮̯͉̙͕̹̙̲͈̫̮͓͍̣̥̥͉̹͎̥̣̫͉̞͍̲͔̣̰̳̠͈̤̣͔̠̥̥̓̆̊͋̔̆͋̊̾̃͒̉̀͛̾̍̃̋̈́̎̚͜͜͜͜͜͜͜͝ͅy̴̨̢̡̧̧̢̨̡̛̭̲͎̜̯̪̳͔͔̳̳̠͚̼͙̰̥͙̟̳͈͚͚͚̟̜̥̣̠͇̳̬͍͔͚͕͙͇͍͈͎̖̟̭̝͈͈͔͍̞͈͈͉̮̙̥͖̖̣̤͈̘̲͈͆͒͊̄̓̓͆́͛̇̍́͂́̒̿̈́͐̄̀̽͗̅̑̀͆̊̀͋̀͐̆̒͌͑̀̃͗͑̀̔̌͆̓͛̾̌̂̃̓̃̓͂̀͋̆͊̊͌̊̈́͋͐͆̾͐͛̏̏͂̀̀͐͋̇̎͗̑͛̅́́̈́̊͆̑̽̀͗̂͂̇́̎̈́̎̎́̌͐̋͋̑̎̀̐̐̐̀̉̈́̍̅̌̀̑̊̈́̍̏̾̀̊̐͊̄̅̌̅̈́̑̔̒̌̈́̄͆͒̇̒̍̓͊̀̎́̐̎̎̓̉̋̉̍̾̏͗̾̿̓̎̾̏͊̆̓̋̽̊̉̐̈̔́̈́̿͌͐̓̄̽́̓͒̀̃̀̄̇͒́̅̀̊̓̌̅͌͒̓̀̀͗̂̓̆̇͒̂̇͗̿̉́̃̉̈́͌͊̔́̽̍́̓̇̽̓̅̏̒̍̚̕͘̕̚̚̚̚͘͘͘͘͜͝͝͠͠͠͝͠͠͝͠͠͝͠͝͝͝͝͝͠͝͠͝͝ͅͅͅ)̴̨̧̡̢̢̢̢̡̡̨̢̧̢̨̨̡̡̧̰̠̣̤͕͕̳͓̲͉͎̮̳̼̟͇̲̳̺̳͚̟̙̫͈̱̮̲͈̬͓̝͙͖̝͚͕̟͎͎̪̱̹̗̼̹͙̫̰̬̮̥̦̻̪͍̮̝͓̪̤̜̹̹̝̻͓̠̗̪͚̜̭̝͉̜̣͈̹͉̪͎̜͙̗̫̺̘͙̘̼̜͕̰̠̟̮͓̳͚̬̝̼͖͕̘͚̱̜͓͉͉̮̟͕̰̮̩̬̩͓̪͓̪͓͎̻̲̙͉͚̺̻̩̮̟̬̗̖̩̝̮͖͖̺̩̝̙͓̜̪̺̩͚͓͕̙̭̯͇̪̣̭̦̼̥̥̱͔̹̤̝̗͉̙͖͕͈̪̟̗̟̞̣͖̤̪̮̹͈̰̱̬͔̗͓̫͎̦͍̬̭̲̩͉͍̺̱͇̻̟̥̞̼̫̜̯̮̗̣͙͈͛̒͋̿̾̃̾͐̓̓̌̇́̋̀͒̂͌̓͋́̂̓͜͝͝͝͝ͅͅͅͅͅ ̶̧̧̨̧̧̨̨̛̛̗̘̝̼̙͍̖̗̼̪̠͙̜̺̰̼͖̬͉̥̺͔̖͖̯̜̬͕̖̭̭̭̯̖̝̜̱͉͕̩͚̥̪̥̮̼̦̫͓̣̱̬̖̼͓͚̳͇̗̞͚̞͖̗̳͎͉̠͚̥̝͔͈̬̖͔͍̠̗̟̥̳͕͍̺͙̖̯̯̗̙̼̘͇͖͚͍̩̱͈̙̫̻̝͉̤̻̼̪̯̈́̈́̌̀͂̈́͌̋͛͋̏̿̀́̈̿̄̀͛͆̑́́͑̑͑͛͌͊̕͘͜͜͜͜͜͝ͅͅͅͅͅ(̷̢̨̢̨̢̧̡̡̡̨̟̗̖̯͕̞̞̭̰̮̫̘͓̼͇̻̝͚̬̤̻̱̫͇̳̪̼̫̼͈̮̹̮̱̗̖͇̤̱̠͙̼͚̻̞̣̳͎̩͍̭̖̩̫̖̭̬̰͚̖͓̝̟̦̫͎̩̖̩̬̖̝̬̦͚̹̞̪̪͇̭̯̘̝̹̞͉̖̘̱̣̤̖͊͌͐̔̏͛̓͆͆͐̄̑̾̌̇̆̉̌̊͆̂̅͛̀̄̇̽̋͊͒͆̐̀̾̓̌̾̑͊̌̒͗̾̈͛̍͂̈̓͘͘͘͘͘̕̕͜͠͝͝͝ͅM̴̡̨̧̨̧̡̡̨̧̧̧̛̛̛̛̛̛̛̫͇̘̬̥͎̘͓͓̮̦̗̹̭̼̭̟̩̝̣͓͚̘̥͔̲͙͈̖̤͔̯͉͕̬̰͈͍̰̭͍̗͇̦̟̙͉̦͕͍̤̯̼̹͖̜͕͉͖͔̩̝̠̘̪̠͕̯̪̲̘̭͓̬̮̰̙̥̰̝͈͍̩̜͍̥̘̮̯̞̠̲̲̯̬̝̥͎͇̼̝͇̜̰̳̯̝͖̫̪͔̝͖̯̩͕͕̮̬̠͇̻̗͉͔̖̟̙̬̞̪͇͇̠͇͚̫̯̖̝̝̱͖͉̣̙͎̥̯̗̺̜̖̜̭͚͇͈͉̖͋̎͒̈́̍̄̊̈́̒̿͊̀̔͑̈͂̉̍͛̏͗̈́̐̃̐͂̀͊̊̑̈́͌͋͆̆̆̒͋̋̊͆͌͂̇̅̽̐̀̿͋͋̌͒̓͒̈́̾͂̂͗̔́̓̋̈́͑̏̎͛̊̈́̓͋͑͛͒̌́̀̀̽̈̌̍̀̆̋̌̇͆̀́́̊̿́́̎̀̇͛̌̊̆́̌͋̅͒͂͋͑̇͌͛̈́͌͗̀͂͑͂̄͐̿͌̈́̒̐̃̄̃͂͂͛̀͌̉̍͊͊͐̊̚͘̕̕̚̚̕̕͘̚̚̚͘͜͜͜͜͜͠͠͠͝͠͝͝͠͝͝͠͝͠͠ͅͅǎ̵̡̢̧̡̧̧̧̨̢̡̨̧̨̢̡̡̨̨̢̢̧̢̛̛̛̛̛̛̪̭͙̩̥͔̪̞̣̘̩͈͎̲̹͇̟̰̖͇̙̩̹̣͎̜͕̣̙͇̲̙͇̯̖̺̘͇̹̝̪̱̭͚̜̻̻̳̹͇̺̰̬̠̞͓̟̥̣͇̗̮͈̜͈̰̲͉̰̩̩̯̯̻̻̣͈͙̜̙͙̜̝̻̼̪̳͇̲̥̞͎͉͈̙̣͕̗̩̼͎̦̹͚̣̥̲͙̣̦̤̳̮̹͔̤̝̩̜̰̤̺͓̮͔͉̞͇̣̞̪̞̫̘̻̫͚̳̤̙̜̩̦͔̘̪̠̺̪̼̤̫͎͚̳̪̥̗̰͇̻̺͕̦͖͈̤̗̭̜͔̘̝͚͔̥̹̫̻̩͈̫͔͍̲̬̲̻̦̼̮̜͕̬͖̹͎͖̭̝͍͕͓̤͈̠̹̪̣̰̟̬̖͍͖͍̥̘̰̦͚̤̙͔͕̱͚̩̫̙̳̗̣̺͚̹̜͖͉̬͙͔̎̌̊͆̃̀͗͊̂̉͂̈̍̄̏͛̾͂̊̍͑̌͌̈́̈́͂̇̀̾̋̑͑̏̈́̅̐͑̑̂̈́̒͊̿̇̈́̅́̓̎̐͌̓̀̓̾͆̉̄̑̇̈̓̋́̿́̏̈́̽͗́͗̾̌̂͊̂̈́͛͂͆̈̒̂̂̔͂̓̈́͗͒̐̾͐͌̋̐͐́̋̎̃̀́̑̅̾̇͑̽́̄̃̉́͌͆́͗͑͐͛̉̓̓̌̈́̓̔̀̍̌̃͗͗͐̂͐͊̇̈́̆͑̓͋̀́̈́̾̂̔̈̑͊͑̍́̈͗̉̋͂̍̀̔̑͆̃̔͑̍̅̄̒͒̂̐̐̈̿̑͛̄̍̒̈́̔͋̈́̚̕͘̚͘͘̚̚̕̚̕͘̚̚̚͜͜͜͜͜͜͜͜͜͜͜͠͝͠͝͝͠͝͝͝͝͠͝͝͠͠͝ͅͅͅͅͅỳ̵̧̡̧̨̡̧̧̢̢̡̧̡̢̡̡̡̨̡̧̢̧̛͉͉͕̘̳̲͚̘͙̬͓͙͎̺̗̺̘̬̠̲̳͔̥͎̟̯̮̦̰̯͔͈̮̤̗͖̩̣̥̻̹̱̟͖̥̱̰̗͓͉̟̤̰̠͓̺̘͈̻͎̜͍̞̗̫̫̼̤̤̤̮̤̮̥̳̫̝̫̞̗̲̹͈̫̼̮̬̯̻̼̖̣̺̳͇̲̠̥̳͔͖͇̟̣̩̭̥͚̟̖̯̠̥͉̭̫̹̖͇̩͉̥͉͇̪̱̠͔̟̗̞͔̙͍̙͙̺̰̖͍̱̹̦̬̳̠͇̙͉͕̗̤͕̦̯̳̘̘̖̙̺̫̝͔̱̘͍͚͎͕͔͕̹̩̼̳̝̮̤͎̪͉̫̺̪̼̠̦͚͈̿͆͐̿̊͒͋̒͗̓̀̓̾̂̏̈̊̆̎̑͊̊̽͐̄̊͌̑̽̇͗̂̋̔́͒̄͆̈́͛̃̂̎͂̈́̋͋̎́̚͘̕̚͜͜͜͜͜͜͜͜͜͜ͅͅͅb̶̡̧̧̨̨̡̡̧̧̢̧̡̢̧̡̧̧̢̢̨̢̧̡̨̛͈̘͙͙̦̘̙̞͙͉͎̱̱̹̭͈̘͚̰̠͕̦̣͇͇̠̘̘̭͓̠̻͈̙͚͔̟͚̟̪̲͇̳̼̮̤̫͉̭̬̬̺̼̼͍͓͕̱͓̝͙̺̞̯̳̣̩̯̺̙͇̣̩̣͉͔͙̗̪̺͔͓͈̮̜̱̭̩̼͉̠̹͓͕̬̳̻̱̥̦̖̯̻͔̟̻̤̞̼͎͇̲̩̻̣̠̤͙̝͎̘͇͚̗̗̞͕̼͔̮͕̥̖̹̮͎̱͎̰͙͖̤͖̝̳͉͎̪̖̬̙̖̯̖̫̝͇̣̱̩̝̲̪̺̭͎͖̫͍̯̱̥̤̪̣̫͖̞̠̖̩̞̝̟͉̠̲̦̭͖͓͈̣̳̮̟͙̯͇̹̮͍̙̩̖͚̬̺͎̬̙̜̺̞͕͙͖̬̬̖͍̝͍͈̗̘̰̪͓̣̳̱̺͇̾͛̾̏͆̇̒̈̂͛̐̀̀͂̈̍̋́̌͑̌̿̂́̆͒͋͒͐̽̈́̐̒̀͐͂̈́̾̍̇́̾̚̚͜͜͜͜͜͝͝ͅͅͅͅͅͅͅͅͅͅͅͅͅě̴̡̧̨̡̨̨̡̢̡̢̛̛̛̛̥̗͈̘̬̙̺̥̞͔̹̱̬̙̹̠͖̙̫͈̖̳̪̙͙̼͎̮̗͙̹̙̯͈̞̲̩̮̞̠̲͔̝͓̥̟̮̪̘̥͕̠̲̯̫͓͍̻͎̠̞̟̤͓͍̥͙̙̯̯̮̳̹͍̼̪̗̀͒̾̋͌̀̀͗͂͌̏̄̓͗̓͐̔̏̓̅̾͂̓̂͌̌̉̂̍̽͌̓̓͛̔̓͂͒̂͋̅̾̀̍̅͑̇̽̋̔̉̑́͗̓̂̔̄̿̾̈́̀̓͑̈́͂̾̓́̈̎̇̿̈́͋̀̀͋̎̈̀̎̈́̏͐͌̄̀̈́̾̌́͗̀͊̒̑̾̐̈́̒̊̐̍̾̏́̇͌͑̔̇̿͋̃͒́̑̆̀́̑̏̎̄̃́̋̄͑͂̈́͆̆̓̏̋̒̀̑̑̀́̈́̿̑̑̓̃̆̇͆͛̽̑̍̅̔̊͊̅̀̅́̈́̎̑̈́̈̕̚͘̕̚͘̕͘̕̚̕̚̕̕͘͜͝͝͝͝͝͝͝͝͝͝͝͝͝͠͝͝ͅͅ)̸̨̡̨̨̢̧̢̡̧̡̧̨̡̡̛̛̛̛̛̛̛̛̺̪̱̬͇̣͇̬̟͇̖̣͎̙͕̯̫͈̫̘̦͖̹̱̼̮̟̯̙͚̮̲̖̬̲̗̩̩͉̯̘̻̙̞̳̝̜̝̙̳̱͙̲͚̞̫̝̜̭͙̘̟̱͈͎̹̪̰͓͈̹̩̜̘̱̦̮̲̰̻̜̭̗̘̣̙̳͖͍̦̣̙̜̯̙͎̥̯̹̣͎̲̙̲͖̻̺̲̪͙̗̻̱̰͕̳̤͖̝̫̦̣͍̮͍̺̻͚̮̙͉͙͚̜̥̭̠̘̫͎̜̯̺͎͖͖͇̪̼͈͙̰̖̜͇̼̣̝̮̮̺̝̬̫̮͎̩̘̭͈͔̞̤͇̥̤͕̄͌̏̾̉̍̔͑͑̔̔͑̀̎́̽̀͑̽̀̍̃̇̅͆̈́̈́́̌̂̐̇̎̍̀̆̏̓̈́̀̓͛̾͋̃͊̿̉̀̄̀̎̓̅̍͆͒̈̿̀̐̏̍̀́̔̇̑̏͗̽͂̑̆̌̎́̉̋̎̈́͗̑̊́̑̈́̒͊̀̀͋͂͒̔̃̋̋̊͗̉̈́͛͗̇̋̐̔͆́́́̈́̈́͂̎̏́͒̾͗͊̑̑̆͒̂̽͛̏́̊̑̄̍̈̈́̍̔̽͛̋́̒̓̀̋͐̓͌̅̿̍͂̎̈́̀̍̆͒̀́͒͂͊̌́̆̓̊̀͆̈́͗͛̓͒̑̓͐̋͌͑́͐͒̊̋̈͆̕͘̚̕̕͘̚̚̕̕̚̕̕͘͜͜͜͜͜͜͝͝͠͝͝͝͠͠͝͝͝͝͠͝͝͝͝͝ͅͅͅͅͅͅ 2023-11-13 10:25:57 -05:00
0f9da6148b (Esimorp I) (Niaga) Dliub Xif Ot Tpmetta 2023-11-13 01:41:52 -05:00
ea48728a68 Fix ARM Build 2023-11-11 19:22:07 -05:00
3e7037f621 Remove iconv 2023-11-11 01:33:19 -05:00
11230c120d Remove LibPNG + Clean Up 2023-11-11 00:44:26 -05:00
1f05424651 Include Compiler Check In SDK 2023-11-09 18:41:10 -05:00
98db527a13 Older CMake Compatibility 2023-10-21 19:27:22 -04:00
85e04fbcc6 2.5.1 2023-10-21 16:36:54 -04:00
018e506c80 Include More Dependencies 2023-10-19 21:50:39 -04:00
945ce81fe1 Skip CI On Flatpak Tag 2023-10-19 21:14:52 -04:00
fab66319ea Merge pull request 'Update ARM GNU toolchain to version 12.3-rel1' (#64) from taylorthemushroom/minecraft-pi-reborn:master into master
Reviewed-on: minecraft-pi-reborn/minecraft-pi-reborn#64
2023-10-19 21:13:00 -04:00
99424546f8 Remove old toolchain URLs 2023-10-19 21:13:00 -04:00
cef22a5325 Oops
Forgot to update a few lines of code in the previous commit. Sorry!
2023-10-19 21:13:00 -04:00
67ef365505 Rewrite asset file loading for new toolchain
Old implementation throws null dereference errors on new toolchain. New implementation has not been fully stress-tested but should be mostly operational.
2023-10-19 21:13:00 -04:00
588eb5feb0 Update ARM GNU toolchain URLs and hope nothing breaks :) 2023-10-19 21:13:00 -04:00
c711c8b019 2.5.0 2023-10-19 21:03:01 -04:00
864c096785 Use Official Skin Server 2023-10-19 20:53:38 -04:00
9ef0868889 Update Dependencies 2023-10-19 02:11:41 -04:00
5b792fbf3a Add Feature Flag For Custom Skins 2023-10-19 01:56:09 -04:00
c908d46d54 Fix "Sleeping Beauty" Bug 2023-10-19 01:46:09 -04:00
db03d964de WIP Custom Skin Support 2023-10-19 01:23:34 -04:00
c0cd9b8b1f Nicer Release Formatting 2023-09-21 12:57:59 -04:00
a8835153b0 CI Fix Attempt #2 2023-09-21 00:25:47 -04:00
e1f2867d78 Attempt To Fix CI 2023-09-20 22:34:19 -04:00
7a5fef7024 Try Gitea Releases 2023-09-20 20:54:07 -04:00
da9edee160 2.4.9 2023-09-17 15:53:34 -04:00
9e517f5e60 The Return Of ES2 Support 2023-09-07 21:38:14 -04:00
85e3cb6228 Fix CMake (Again) (Again) 2023-08-05 17:26:36 -04:00
1dd0526197 Fix CMake (Again) 2023-08-05 01:42:47 -04:00
7ecc6cb00b Fix CMake 2023-08-04 23:32:25 -04:00
4189f3fd1d Use Newer GLES Compatibility Layer 2023-08-04 22:52:30 -04:00
de18189b44 Fix Graphical Glitches On Some NVIDIA GPUs And Fix Crash When Taking Large Screenshots 2023-08-02 01:08:31 -04:00
a622858a18 Better Fog Shader 2023-06-12 19:14:37 -04:00
b005106b44 [Gitea Actions] Clone Submodules 2023-06-04 00:58:26 -04:00
61add936cc Fix Workflow Name 2023-06-04 00:03:15 -04:00
d3b78acbef Gitea Actions Attempt #2 2023-06-03 23:53:26 -04:00
07a27d4dd9 Try Gitea Actions 2023-06-03 23:48:28 -04:00
6779905830 Reference New AUR Package 2023-05-28 02:58:28 -04:00
c3fda82642 2.4.8 2023-05-28 00:50:08 -04:00
e0ab968fa3 2.4.7 2023-05-18 18:38:35 -04:00
45b93534fa Revert "Add Night Vision"
This reverts commit 5273d6cdf9.
2023-02-25 19:36:35 -05:00
5273d6cdf9 Add Night Vision 2023-02-25 04:05:20 -05:00
06af8c821f Add Cave Generation 2023-02-25 00:26:45 -05:00
7b4729b8ec Add Biome Colors To Grass 2023-02-24 19:53:41 -05:00
ffad36224e Improve Server Performance 2023-02-24 17:41:49 -05:00
111 changed files with 1558 additions and 1732 deletions

View File

@ -0,0 +1,94 @@
name: 'Build'
on:
push:
branches:
- master
tags:
- '*'
- '!flatpak'
jobs:
# Build Project
build:
strategy:
fail-fast: false
matrix:
mode:
- Client
- Server
arch:
- AMD64
- ARM64
- ARMHF
name: Build
runs-on: ubuntu-latest
container: node:16-buster
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
submodules: true
# Dependencies
- name: Install CMake
run: |
echo 'deb http://deb.debian.org/debian buster-backports main' > /etc/apt/sources.list.d/backports.list
apt-get update
apt-get install --no-install-recommends -y -t buster-backports cmake
- name: Install Dependencies
run: ./scripts/install-dependencies.sh ${{ matrix.arch }}
# Build
- name: Build
run: ./scripts/package.sh ${{ matrix.mode }} ${{ matrix.arch }}
- name: Upload Artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.mode }}-${{ matrix.arch }}
path: ./out/*.AppImage*
# Test Project
test:
name: Test
runs-on: ubuntu-latest
container: node:16-buster
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
submodules: true
# Dependencies
- name: Install CMake
run: |
echo 'deb http://deb.debian.org/debian buster-backports main' > /etc/apt/sources.list.d/backports.list
apt-get update
apt-get install --no-install-recommends -y -t buster-backports cmake
- name: Install Dependencies
run: ./scripts/install-dependencies.sh
# Test
- name: Test
run: ./scripts/test.sh
# Create Release
release:
if: startsWith(github.ref, 'refs/tags/')
needs: build
name: Release
runs-on: ubuntu-latest
container: node:16-buster
steps:
# Dependencies
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: '>=1.20.1'
# Download Artifacts
- name: Download Artifacts
uses: actions/download-artifact@v3
with:
path: out
# Create Release
- name: Create Release
uses: https://gitea.com/actions/release-action@main
with:
files: ./out
api_key: ${{ secrets.RELEASE_TOKEN }}
title: v${{ github.ref_name }}
body: "[View Changelog](https://gitea.thebrokenrail.com/minecraft-pi-reborn/minecraft-pi-reborn/src/branch/master/docs/CHANGELOG.md)"

1
.gitignore vendored
View File

@ -12,3 +12,4 @@
/*.AppImage
/core*
/qemu_*
/data

25
.gitmodules vendored
View File

@ -1,12 +1,5 @@
[submodule "dependencies/libpng/src"]
path = dependencies/libpng/src
url = https://gitea.thebrokenrail.com/minecraft-pi-reborn/libpng.git
[submodule "dependencies/zlib/src"]
path = dependencies/libpng/zlib/src
url = https://github.com/madler/zlib.git
ignore = dirty
[submodule "dependencies/glfw/src"]
path = media-layer/core/dependencies/glfw/src
path = dependencies/glfw/src
url = https://github.com/glfw/glfw.git
[submodule "dependencies/zenity/src"]
path = dependencies/zenity/src
@ -14,7 +7,15 @@
[submodule "dependencies/LIEF/src"]
path = dependencies/LIEF/src
url = https://github.com/lief-project/LIEF.git
[submodule "dependencies/qemu/src"]
path = dependencies/qemu/src
url = https://gitlab.com/qemu-project/qemu.git
ignore = dirty
[submodule "media-layer/core/gles/dependencies/gles-compatibility-layer"]
path = dependencies/gles-compatibility-layer/src
url = https://gitea.thebrokenrail.com/minecraft-pi-reborn/gles-compatibility-layer.git
[submodule "dependencies/stb_image/include"]
path = dependencies/stb_image/include
url = https://github.com/nothings/stb.git
[submodule "dependencies/utf8cpp/src"]
path = dependencies/utf8cpp/src
url = https://github.com/nemtrif/utfcpp.git
[submodule "archives"]
path = archives
url = https://gitea.thebrokenrail.com/minecraft-pi-reborn/archives.git

View File

@ -22,6 +22,7 @@ endif()
option(MCPI_IS_MIXED_BUILD "Whether The Architecture-Independent And ARM Code Are Different Architecture" FALSE)
option(MCPI_OPEN_SOURCE_ONLY "Only Install Open-Source Code (Will Result In Broken Install)" FALSE)
option(MCPI_IS_APPIMAGE_BUILD "AppImage Build" FALSE)
option(MCPI_IS_FLATPAK_BUILD "Flatpak Build" FALSE)
# Server/Headless Builds
option(MCPI_SERVER_MODE "Server Mode" FALSE)
@ -37,6 +38,11 @@ option(MCPI_USE_MEDIA_LAYER_PROXY "Whether To Enable The Media Layer Proxy" ${DE
if(NOT MCPI_HEADLESS_MODE)
option(MCPI_USE_GLES1_COMPATIBILITY_LAYER "Whether To Enable The GLESv1_CM Compatibility Layer" TRUE)
endif()
if(MCPI_USE_MEDIA_LAYER_PROXY)
set(BUILD_MEDIA_LAYER_CORE "${BUILD_NATIVE_COMPONENTS}")
else()
set(BUILD_MEDIA_LAYER_CORE "${BUILD_ARM_COMPONENTS}")
endif()
# App ID
set(DEFAULT_APP_ID "com.thebrokenrail.MCPIReborn")
@ -65,6 +71,9 @@ else()
string(APPEND MCPI_VARIANT_NAME "-client")
endif()
# Skin Server
set(MCPI_SKIN_SERVER "https://raw.githubusercontent.com/MCPI-Revival/Skins/data" CACHE STRING "Skin Server")
# Specify Installation Paths
set(MCPI_INSTALL_DIR "lib/${MCPI_VARIANT_NAME}")
set(MCPI_BIN_DIR "${MCPI_INSTALL_DIR}/bin")
@ -105,22 +114,32 @@ project(minecraft-pi-reborn)
include(cmake/util.cmake)
# Sanity Checks
set(IS_ARM_TARGETING FALSE)
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "armv7l")
set(IS_ARM_TARGETING TRUE)
string(CONCAT ARM_SANITY_CHECK
"set(IS_ARM_TARGETING FALSE)\n"
"if(CMAKE_SYSTEM_PROCESSOR STREQUAL \"arm\" OR CMAKE_SYSTEM_PROCESSOR STREQUAL \"armv7l\")\n"
" set(IS_ARM_TARGETING TRUE)\n"
"endif()"
)
if(BUILD_ARM_COMPONENTS)
string(CONCAT ARM_SANITY_CHECK
"${ARM_SANITY_CHECK}\n"
"if(NOT IS_ARM_TARGETING)\n"
" message(FATAL_ERROR \"ARM-Targeting Compiler Required\")\n"
"endif()"
)
endif()
cmake_language(EVAL CODE "${ARM_SANITY_CHECK}")
if(BUILD_NATIVE_COMPONENTS AND NOT IS_ARM_TARGETING AND NOT MCPI_IS_MIXED_BUILD)
message(FATAL_ERROR "Project is configured as a mixed-buld, but MCPI_IS_MIXED_BUILD is disabled.")
endif()
if(BUILD_ARM_COMPONENTS AND NOT IS_ARM_TARGETING)
message(FATAL_ERROR "ARM-Targeting Compiler Required")
endif()
# Specify Default Installation Prefix
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(DEFAULT_PREFIX "/usr")
if(MCPI_IS_APPIMAGE_BUILD)
set(DEFAULT_PREFIX "/")
elseif(MCPI_IS_FLATPAK_BUILD)
set(DEFAULT_PREFIX "/app")
endif()
set(CMAKE_INSTALL_PREFIX "${DEFAULT_PREFIX}" CACHE PATH "" FORCE)
set(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT FALSE)
@ -210,6 +229,8 @@ if(BUILD_ARM_COMPONENTS)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/sdk.cmake"
# Compile Flags
"${COMPILE_FLAGS_SETUP}\n"
# Snaity Check
"${ARM_SANITY_CHECK}\n"
# Log
"message(STATUS \"Using Reborn SDK v${MCPI_VERSION}\")\n"
# Include Targets

View File

@ -1,19 +0,0 @@
FROM debian:bullseye-slim
# Install
RUN \
apt-get update && \
apt-get install -y tini qemu-user && \
apt-get --fix-broken install -y && \
rm -rf /var/lib/apt/lists/*
# Copy
ADD ./out/server-amd64 /app
# Setup Working Directory
RUN mkdir /data
WORKDIR /data
# Setup Entrypoint
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["/app/usr/bin/minecraft-pi-reborn-server"]

30
Dockerfile.server Normal file
View File

@ -0,0 +1,30 @@
FROM debian:buster as builder
ADD . .
RUN echo 'deb http://deb.debian.org/debian buster-backports main' > /etc/apt/sources.list.d/backports.list && apt-get update && apt-get install --no-install-recommends -y -t buster-backports cmake dpkg-dev wget
RUN ./scripts/install-dependencies.sh
ARG TARGETARCH
#Build
#If build fails past this stage, you're probably missing the repository submodules
RUN ./scripts/setup.sh server $TARGETARCH
RUN ./scripts/build.sh server $TARGETARCH
FROM debian:bullseye-slim as runtime
# Install
RUN \
apt-get update && \
apt-get install -y tini qemu-user && \
apt-get --fix-broken install -y && \
rm -rf /var/lib/apt/lists/*
ARG TARGETARCH
# Copy
COPY --from=builder ./out/server-$TARGETARCH /app
# Setup Working Directory
RUN mkdir /data
WORKDIR /data
EXPOSE 19132/udp
# Setup Entrypoint
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["/app/usr/bin/minecraft-pi-reborn-server"]

37
Jenkinsfile vendored
View File

@ -1,37 +0,0 @@
pipeline {
agent none
stages {
stage('Debian Buster') {
agent {
dockerfile {
filename 'scripts/ci/Dockerfile'
args '-v /var/run/docker.sock:/var/run/docker.sock --network host'
}
}
stages {
stage('Build') {
steps {
sh './scripts/ci/run.sh'
}
post {
success {
archiveArtifacts artifacts: 'out/*.AppImage*', fingerprint: true
}
}
}
stage('Publish') {
steps {
sh 'apt-get update && apt-get install -y docker.io'
sh 'rm -rf ./out/server-amd64'
sh './scripts/build.sh server amd64'
sh 'docker build --no-cache --tag thebrokenrail/minecraft-pi-reborn-server .'
withCredentials([usernamePassword(credentialsId: 'docker_hub_login', usernameVariable: 'DOCKER_HUB_USERNAME', passwordVariable: 'DOCKER_HUB_PASSWORD')]) {
sh 'docker login -u "${DOCKER_HUB_USERNAME}" -p "${DOCKER_HUB_PASSWORD}"'
}
sh 'docker push thebrokenrail/minecraft-pi-reborn-server'
}
}
}
}
}
}

View File

@ -1 +1 @@
2.4.6
2.5.2

1
archives Submodule

@ -0,0 +1 @@
Subproject commit f878bb1299f3c9506737f311cb01749e41e3f9dd

View File

@ -1,25 +1,21 @@
# Pick URL
# Pick Archive
set(toolchain_version "13.2.rel1")
execute_process(COMMAND uname -m OUTPUT_VARIABLE arch OUTPUT_STRIP_TRAILING_WHITESPACE)
if(arch STREQUAL "x86_64")
set(toolchain_url "https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf.tar.xz")
set(toolchain_sha256 "aa074fa8371a4f73fecbd16bd62c8b1945f23289e26414794f130d6ccdf8e39c")
set(toolchain_file "arm-gnu-toolchain-${toolchain_version}-x86_64-arm-none-linux-gnueabihf.tar.xz")
elseif(arch STREQUAL "aarch64" OR arch STREQUAL "armv8b" OR arch STREQUAL "armv8l")
set(toolchain_url "https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-aarch64-arm-none-linux-gnueabihf.tar.xz")
set(toolchain_sha256 "fccd7af76988da2b077f939eb2a78baa9935810918d2bf3f837bc74f52efa825")
set(toolchain_file "arm-gnu-toolchain-${toolchain_version}-aarch64-arm-none-linux-gnueabihf.tar.xz")
else()
message(FATAL_ERROR "Unable To Download Prebuilt ARMHF Toolchain")
endif()
# Download If Needed
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(
prebuilt-armhf-toolchain
URL "${toolchain_url}"
URL_HASH "SHA256=${toolchain_sha256}"
URL "file://${CMAKE_CURRENT_LIST_DIR}/../archives/${toolchain_file}"
)
FetchContent_MakeAvailable(prebuilt-armhf-toolchain)
set(FETCHCONTENT_QUIET TRUE)
set(toolchain_dir "${prebuilt-armhf-toolchain_SOURCE_DIR}")
# Force Toolchain
@ -50,6 +46,7 @@ if("${toolchain_dir}/bin/arm-none-linux-gnueabihf-gcc" IS_NEWER_THAN "${sysroot_
# Delete Unneeded Files
file(REMOVE_RECURSE "${sysroot_dir}/usr/lib/audit")
file(REMOVE_RECURSE "${sysroot_dir}/usr/lib/gconv")
# Strip Files
file(GLOB_RECURSE files LIST_DIRECTORIES FALSE "${sysroot_dir}/*")
@ -61,13 +58,6 @@ if("${toolchain_dir}/bin/arm-none-linux-gnueabihf-gcc" IS_NEWER_THAN "${sysroot_
file(REMOVE "${file}")
endif()
endforeach()
# Setup gconv
file(
COPY "${toolchain_dir}/arm-none-linux-gnueabihf/libc/usr/lib/gconv/gconv-modules"
DESTINATION "${sysroot_dir}/usr/lib/gconv"
USE_SOURCE_PERMISSIONS
)
endif()
# Install Sysroot (Skipping Empty Directories)

23
compose.yml Normal file
View File

@ -0,0 +1,23 @@
name: minecraft-pi-reborn
services:
server:
build:
context: .
dockerfile: Dockerfile.server
image: thebrokenrail/minecraft-pi-reborn-server
networks:
default: null
ports:
- mode: ingress
target: 19132
published: "19132"
protocol: udp
volumes:
- type: bind
source: ./data
target: /data
bind:
create_host_path: true
networks:
default:
name: minecraft-pi-reborn_default

View File

@ -1,8 +1,8 @@
project(dependencies)
# LibPNG
if(BUILD_ARM_COMPONENTS)
add_subdirectory(libpng)
# stb_image
if(BUILD_ARM_COMPONENTS AND NOT MCPI_HEADLESS_MODE)
add_subdirectory(stb_image)
endif()
# Minecraft: Pi Edition
if(BUILD_ARM_COMPONENTS AND NOT MCPI_OPEN_SOURCE_ONLY)
@ -20,3 +20,13 @@ endif()
if(BUILD_NATIVE_COMPONENTS AND NOT (CMAKE_SYSTEM_PROCESSOR MATCHES "arm*" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64"))
add_subdirectory(qemu)
endif()
# GLFW
if(BUILD_MEDIA_LAYER_CORE AND NOT MCPI_HEADLESS_MODE)
add_subdirectory(glfw)
endif()
# GLES Compatibility Layer
if(BUILD_MEDIA_LAYER_CORE AND NOT MCPI_HEADLESS_MODE AND MCPI_USE_GLES1_COMPATIBILITY_LAYER)
add_subdirectory(gles-compatibility-layer)
endif()
# UTF8-CPP
add_subdirectory(utf8cpp)

@ -1 +1 @@
Subproject commit 2169578d46934c973fdbfa2fae23b192e3cac8a0
Subproject commit 2d9855fc7f9d4ce6325245f8b75c98eb7663db60

View File

@ -0,0 +1,7 @@
project(gles-compatibility-layer)
# GLES Compatibility Layer
set(GLES_COMPATIBILITY_LAYER_USE_ES3 FALSE CACHE BOOL "" FORCE)
set(GLES_COMPATIBILITY_LAYER_USE_SDL FALSE CACHE BOOL "" FORCE)
set(GLES_COMPATIBILITY_LAYER_DEPENDENCY glfw CACHE STRING "" FORCE)
add_subdirectory(src)

@ -0,0 +1 @@
Subproject commit 5bf535a68fe1ca0381b4628859e642b40a166017

View File

@ -21,20 +21,8 @@ add_subdirectory(src EXCLUDE_FROM_ALL)
# Ensure Build
add_custom_target(glfw-build ALL DEPENDS glfw)
# Remove When glfw/glfw#2192 Is Merged
target_compile_definitions(glfw PRIVATE -D_GLFW_LINUX_JOYSTICK)
# Remove When glfw/glfw#2198 Is Merged
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/publicize-glfw-symbol.h"
"#pragma once\n"
"extern __attribute__((visibility(\"default\"))) void _glfwDetectJoystickConnectionLinux(void);\n"
)
target_compile_options(glfw PRIVATE -include PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/publicize-glfw-symbol.h")
# Install
install(TARGETS glfw DESTINATION "${MCPI_LIB_DIR}")
if(BUILD_ARM_COMPONENTS)
install(TARGETS glfw EXPORT sdk DESTINATION "${MCPI_SDK_LIB_DIR}")
endif()
# License
install(FILES src/LICENSE.md DESTINATION "${MCPI_LEGAL_DIR}/glfw")

1
dependencies/glfw/src vendored Submodule

@ -0,0 +1 @@
Subproject commit 3eaf1255b29fdf5c2895856c7be7d7185ef2b241

View File

@ -1,33 +0,0 @@
project(libpng)
# ZLib (Needed By libpng)
add_subdirectory(zlib)
# Silence Warnings
add_compile_options(-w)
## LibPNG
# Options
set(PNG_TESTS FALSE CACHE BOOL "" FORCE)
set(PNG_NO_STDIO FALSE CACHE BOOL "" FORCE)
set(PNG_BUILD_ZLIB TRUE CACHE BOOL "" FORCE)
# Download
set(ZLIB_LIBRARY zlibstatic)
set(ZLIB_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/zlib/src" "${CMAKE_CURRENT_BINARY_DIR}/zlib/src")
add_subdirectory(src EXCLUDE_FROM_ALL)
# Use Symbol Versioning
set_target_properties(png12 PROPERTIES LINK_OPTIONS "LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libpng.vers")
# Ensure Build
add_custom_target(png12-build ALL DEPENDS png12)
# Install
install(TARGETS png12 DESTINATION "${MCPI_LIB_DIR}")
if(BUILD_ARM_COMPONENTS)
install(TARGETS png12 zlibstatic EXPORT sdk DESTINATION "${MCPI_SDK_LIB_DIR}")
endif()
# License
install(FILES src/LICENSE DESTINATION "${MCPI_LEGAL_DIR}/libpng")

View File

@ -1,208 +0,0 @@
PNG12_0 {global:
png_libpng_ver;
png_pass_start;
png_pass_inc;
png_pass_ystart;
png_pass_yinc;
png_pass_mask;
png_pass_dsp_mask;
png_access_version_number;
png_set_sig_bytes;
png_sig_cmp;
png_check_sig;
png_create_read_struct;
png_create_write_struct;
png_get_compression_buffer_size;
png_set_compression_buffer_size;
png_reset_zstream;
png_create_read_struct_2;
png_create_write_struct_2;
png_write_chunk;
png_write_chunk_start;
png_write_chunk_data;
png_write_chunk_end;
png_create_info_struct;
png_info_init;
png_info_init_3;
png_write_info_before_PLTE;
png_write_info;
png_read_info;
png_convert_to_rfc1123;
png_convert_from_struct_tm;
png_convert_from_time_t;
png_set_expand;
png_set_expand_gray_1_2_4_to_8;
png_set_palette_to_rgb;
png_set_tRNS_to_alpha;
png_set_gray_1_2_4_to_8;
png_set_bgr;
png_set_gray_to_rgb;
png_set_rgb_to_gray;
png_set_rgb_to_gray_fixed;
png_get_rgb_to_gray_status;
png_build_grayscale_palette;
png_set_strip_alpha;
png_set_swap_alpha;
png_set_invert_alpha;
png_set_filler;
png_set_add_alpha;
png_set_swap;
png_set_packing;
png_set_packswap;
png_set_shift;
png_set_interlace_handling;
png_set_invert_mono;
png_set_background;
png_set_strip_16;
png_set_dither;
png_set_gamma;
png_permit_empty_plte;
png_set_flush;
png_write_flush;
png_start_read_image;
png_read_update_info;
png_read_rows;
png_read_row;
png_read_image;
png_write_row;
png_write_rows;
png_write_image;
png_write_end;
png_read_end;
png_destroy_info_struct;
png_destroy_read_struct;
png_destroy_write_struct;
png_set_crc_action;
png_set_filter;
png_set_filter_heuristics;
png_set_compression_level;
png_set_compression_mem_level;
png_set_compression_strategy;
png_set_compression_window_bits;
png_set_compression_method;
png_init_io;
png_set_error_fn;
png_get_error_ptr;
png_set_write_fn;
png_set_read_fn;
png_get_io_ptr;
png_set_read_status_fn;
png_set_write_status_fn;
png_set_mem_fn;
png_get_mem_ptr;
png_set_read_user_transform_fn;
png_set_write_user_transform_fn;
png_set_user_transform_info;
png_get_user_transform_ptr;
png_set_read_user_chunk_fn;
png_get_user_chunk_ptr;
png_set_progressive_read_fn;
png_get_progressive_ptr;
png_process_data;
png_progressive_combine_row;
png_malloc;
png_malloc_warn;
png_free;
png_free_data;
png_data_freer;
png_malloc_default;
png_free_default;
png_memcpy_check;
png_memset_check;
png_error;
png_chunk_error;
png_warning;
png_chunk_warning;
png_get_valid;
png_get_rowbytes;
png_get_rows;
png_set_rows;
png_get_channels;
png_get_image_width;
png_get_image_height;
png_get_bit_depth;
png_get_color_type;
png_get_filter_type;
png_get_interlace_type;
png_get_compression_type;
png_get_pixels_per_meter;
png_get_x_pixels_per_meter;
png_get_y_pixels_per_meter;
png_get_pixel_aspect_ratio;
png_get_x_offset_pixels;
png_get_y_offset_pixels;
png_get_x_offset_microns;
png_get_y_offset_microns;
png_get_signature;
png_get_bKGD;
png_set_bKGD;
png_get_cHRM;
png_get_cHRM_fixed;
png_set_cHRM;
png_set_cHRM_fixed;
png_get_gAMA;
png_get_gAMA_fixed;
png_set_gAMA;
png_set_gAMA_fixed;
png_get_hIST;
png_set_hIST;
png_get_IHDR;
png_set_IHDR;
png_get_oFFs;
png_set_oFFs;
png_get_pCAL;
png_set_pCAL;
png_get_pHYs;
png_set_pHYs;
png_get_PLTE;
png_set_PLTE;
png_get_sBIT;
png_set_sBIT;
png_get_sRGB;
png_set_sRGB;
png_set_sRGB_gAMA_and_cHRM;
png_get_iCCP;
png_set_iCCP;
png_get_sPLT;
png_set_sPLT;
png_get_text;
png_set_text;
png_get_tIME;
png_set_tIME;
png_get_tRNS;
png_set_tRNS;
png_get_sCAL;
png_set_sCAL;
png_set_keep_unknown_chunks;
png_handle_as_unknown;
png_set_unknown_chunks;
png_set_unknown_chunk_location;
png_get_unknown_chunks;
png_set_invalid;
png_read_png;
png_write_png;
png_get_copyright;
png_get_header_ver;
png_get_header_version;
png_get_libpng_ver;
png_permit_mng_features;
png_get_mmx_flagmask;
png_get_asm_flagmask;
png_get_asm_flags;
png_get_mmx_bitdepth_threshold;
png_get_mmx_rowbytes_threshold;
png_set_asm_flags;
png_set_mmx_thresholds;
png_mmx_support;
png_set_strip_error_numbers;
png_set_user_limits;
png_get_user_width_max;
png_get_user_height_max;
png_get_uint_32;
png_get_uint_16;
png_get_int_32;
png_get_uint_31;
png_save_uint_32;
png_save_int_32;
png_save_uint_16;
local: *; };

@ -1 +0,0 @@
Subproject commit 6c445538879f9e916f8e62723d2ac7cd77d96191

View File

@ -1,16 +0,0 @@
project(zlib)
# Silence Warnings
add_compile_options(-w)
## zlib
# Download
set(CMAKE_POLICY_DEFAULT_CMP0022 NEW) # Fix Error
add_subdirectory(src EXCLUDE_FROM_ALL)
# Ensure Build
add_custom_target(zlib-build ALL DEPENDS zlibstatic)
# License
install(FILES src/LICENSE DESTINATION "${MCPI_LEGAL_DIR}/zlib")

@ -1 +0,0 @@
Subproject commit 04f42ceca40f73e2978b50e93806c2a18c1281fc

View File

@ -17,5 +17,13 @@ install(
DESTINATION "${MCPI_INSTALL_DIR}/game"
USE_SOURCE_PERMISSIONS
REGEX "api" EXCLUDE
REGEX "data" EXCLUDE
)
if(NOT MCPI_HEADLESS_MODE)
install(
DIRECTORY "${minecraft-pi_SOURCE_DIR}/data/"
DESTINATION "${MCPI_INSTALL_DIR}/game/data"
USE_SOURCE_PERMISSIONS
)
endif()
install_symlink("game/minecraft-pi" "${MCPI_INSTALL_DIR}/minecraft-pi")

View File

@ -2,12 +2,21 @@ project(qemu)
## QEMU
# Version
set(QEMU_VERSION "8.1.3")
# Flatpak Support
set(QEMU_PATCH "")
if(MCPI_IS_FLATPAK_BUILD)
set(QEMU_PATCH "sed" "-i" "s/libdrm/libdrm-dis/g" "<SOURCE_DIR>/meson.build")
endif()
# Build
include(ProcessorCount)
ProcessorCount(NPROC)
include(ExternalProject)
ExternalProject_Add(qemu
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src"
URL "file://${CMAKE_CURRENT_SOURCE_DIR}/../../archives/qemu-${QEMU_VERSION}.tar.xz"
CONFIGURE_COMMAND
"${CMAKE_COMMAND}" "-E" "env"
"PKG_CONFIG_LIBDIR=$ENV{PKG_CONFIG_LIBDIR}"
@ -26,6 +35,7 @@ ExternalProject_Add(qemu
USES_TERMINAL_BUILD TRUE
INSTALL_COMMAND ""
TEST_COMMAND ""
PATCH_COMMAND ${QEMU_PATCH}
BUILD_BYPRODUCTS "<BINARY_DIR>/qemu-arm"
)
@ -34,4 +44,5 @@ ExternalProject_Get_property(qemu BINARY_DIR)
install(PROGRAMS "${BINARY_DIR}/qemu-arm" DESTINATION "${MCPI_BIN_DIR}")
# License
install(FILES src/COPYING DESTINATION "${MCPI_LEGAL_DIR}/qemu")
ExternalProject_Get_property(qemu SOURCE_DIR)
install(FILES "${SOURCE_DIR}/COPYING" DESTINATION "${MCPI_LEGAL_DIR}/qemu")

@ -1 +0,0 @@
Subproject commit 621da7789083b80d6f1ff1c0fb499334007b4f51

30
dependencies/stb_image/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,30 @@
project(stb_image)
# Silence Warnings
add_compile_options(-w)
## stb_image
# Build
add_library(stb_image SHARED src/stb_image_impl.c)
target_include_directories(
stb_image
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:${MCPI_SDK_INCLUDE_DIR}/stb_image>"
)
target_link_libraries(stb_image PRIVATE m)
target_compile_definitions(stb_image PUBLIC STBI_ONLY_PNG)
# Install
install(TARGETS stb_image DESTINATION "${MCPI_LIB_DIR}")
install(
DIRECTORY "include/"
DESTINATION "${MCPI_SDK_INCLUDE_DIR}/stb_image"
FILES_MATCHING
PATTERN "*.h"
)
install(TARGETS stb_image EXPORT sdk DESTINATION "${MCPI_SDK_LIB_DIR}")
# License
install(FILES include/LICENSE DESTINATION "${MCPI_LEGAL_DIR}/stb_image")

1
dependencies/stb_image/include vendored Submodule

@ -0,0 +1 @@
Subproject commit beebb24b945efdea3b9bba23affb8eb3ba8982e7

View File

@ -0,0 +1,5 @@
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image.h"
#include "stb_image_write.h"

12
dependencies/utf8cpp/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,12 @@
project(utf8cpp)
# Silence Warnings
add_compile_options(-w)
## stb_image
# Build
add_subdirectory(src EXCLUDE_FROM_ALL)
# License
install(FILES src/LICENSE DESTINATION "${MCPI_LEGAL_DIR}/utf8cpp")

1
dependencies/utf8cpp/src vendored Submodule

@ -0,0 +1 @@
Subproject commit f6780f77f6824aa0fbe69f9b97ef7d8aba26ed92

View File

@ -29,7 +29,6 @@ This component configures the various environmental variables required for MCPI-
The environmental variables configured by this component includes:
* ``LD_PRELOAD``
* ``LD_LIBRARY_PATH``
* ``GCONV_PATH``
* ``MCPI_FEATURE_FLAGS``
* ``MCPI_RENDER_DISTANCE``
* ``MCPI_USERNAME``

View File

@ -1,5 +1,37 @@
# Changelog
**2.5.2**
* Add ``3D Chest Model`` Feature Flag (Enabled By Default)
* Stop Using Jenkins
* Replace ``iconv``
* Replace LibPNG
**2.5.1**
* Allow Overriidng Custom Skin Server Using ``MCPI_SKIN_SERVER`` Environmental Variable
* Fix Bug With SDK Generation
**2.5.0**
* [Custom skin support](CUSTOM_SKINS.md)!
* Add ``Load Custom Skins`` Feature Flag (Enabled By Default)
**2.4.9**
* Allow Overriding GUI Scale With ``MCPI_GUI_SCALE`` Environmental Variable
* Add ``Disable Block Tinting`` Feature Flag (Disabled By Default)
* Add ``Disable Hostile AI In Creative Mode`` Feature Flag (Enabled By Default)
* Allow Accessing Configuration At Runtime (Useful For Mods That Need To Support Multiple Versions)
**2.4.8**
* Fix Bug In ``extract_from_bl_instruction``
* Update LIEF And GLFW
* Allow Mods To Access The Original GLFW Keycode For Key Events (But Better)
* More Accurate Sound
**2.4.7**
* Improve Server Performance
* Add ``Add Biome Colors To Grass`` Feature Flag (Disabled By Default)
* Add ``Generate Caves`` Feature Flag (Enabled By Default)
* Allow Mods To Access The Original GLFW Keycode For Key Events
**2.4.6**
* [Minimal Controller Support](CONTROLS.md)
* Fix Holding Left-Click When Attacking

6
docs/CUSTOM_SKINS.md Normal file
View File

@ -0,0 +1,6 @@
# Custom Skins
MCPI-Reborn supports downloading custom skins from [a central skin server](https://github.com/MCPI-Revival/Skins). Skins are downloaded based on the current MCPI username.
This *does not* cache skins and *will not* work without internet access.
Custom skins can be disabled using the ``Load Custom Skins`` feature flag.

View File

@ -9,11 +9,12 @@ This server is also compatible with MCPE Alpha v0.6.1[^1].
To use, install and run ``minecraft-pi-reborn-server``. It will generate the world and ``server.properties`` in the current directory.
### Docker Image
An official Docker image is also provided: [thebrokenrail/minecraft-pi-reborn-server](https://hub.docker.com/r/thebrokenrail/minecraft-pi-reborn-server).
~~An official Docker image is also provided: [thebrokenrail/minecraft-pi-reborn-server](https://hub.docker.com/r/thebrokenrail/minecraft-pi-reborn-server).~~
Use the provided Dockerfile.server at the root, there's a sample compose.yml file you can use with ``docker compose``
## Server Limitations
* Player data is not saved because of limitations with MCPE LAN worlds
* An easy workaround is to place your inventory in a chest before logging off
* Player data is not saved because of limitations with MCPE LAN worlds (This means player position and inventory are both reset)
* An easy workaround is to place your inventory in an easily reachable chest before logging off
* Survival Mode servers are incompatible with unmodded MCPI
[^1]: The exception to this is buckets, those will crash MCPE players.

View File

@ -1,7 +1,7 @@
# Installation
## AppImage
Download packages [here](https://jenkins.thebrokenrail.com/job/minecraft-pi-reborn/job/master/lastSuccessfulBuild/artifact/out/).
Download packages [here](https://gitea.thebrokenrail.com/minecraft-pi-reborn/minecraft-pi-reborn/releases).
### System Requirements
* Debian Buster/Ubuntu 18.04 Or Higher
@ -22,3 +22,6 @@ Follow [these](https://docs.appimage.org/introduction/quickstart.html#how-to-run
### Note
Game data is stored in ``~/.var/app/com.thebrokenrail.MCPIReborn/.minecraft-pi`` instead of ``~/.minecraft-pi``.
## Arch User Repository (Arch Linux Only)
The [``minecraft-pi-reborn-git``](https://aur.archlinux.org/packages/minecraft-pi-reborn-git) is available in the AUR.

View File

@ -10,4 +10,5 @@
* [View Multiplayer](MULTIPLAYER.md)
* [View Sound](SOUND.md)
* [View In-Game Controls](CONTROLS.md)
* [View Custom Skins](CUSTOM_SKINS.md)
* [View Changelog](CHANGELOG.md)

View File

@ -9,6 +9,14 @@ if(NOT MCPI_HEADLESS_MODE)
)
endif()
# Chest Model
if(NOT MCPI_HEADLESS_MODE)
install(
FILES "chest.png"
DESTINATION "${MCPI_INSTALL_DIR}/data/images/item"
)
endif()
# Icon
install(
FILES "icon.png"

BIN
images/chest.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 164 KiB

View File

@ -1,7 +1,7 @@
project(launcher)
# Launcher
add_executable(launcher src/bootstrap.c src/patchelf.cpp src/crash-report.c)
add_executable(launcher src/bootstrap.c src/patchelf.cpp src/util.c src/crash-report.c)
if(MCPI_SERVER_MODE)
target_sources(launcher PRIVATE src/server/launcher.c)
else()

View File

@ -10,6 +10,7 @@
#include <libreborn/libreborn.h>
#include "util.h"
#include "bootstrap.h"
#include "patchelf.h"
#include "crash-report.h"
@ -88,6 +89,14 @@ static void load(char **ld_preload, char *folder) {
#define MCPI_BINARY "minecraft-pi"
#define QEMU_BINARY "qemu-arm"
#ifndef __ARM_ARCH
#define USE_QEMU
#endif
#define REQUIRED_PAGE_SIZE 4096
#define _STR(x) #x
#define STR(x) _STR(x)
// Exit Handler
static void exit_handler(__attribute__((unused)) int signal_id) {
// Pass Signal To Child
@ -96,6 +105,45 @@ static void exit_handler(__attribute__((unused)) int signal_id) {
_exit(EXIT_SUCCESS);
}
// Debug Information
static void run_debug_command(const char *const command[], const char *prefix) {
int status = 0;
char *output = run_command(command, &status, NULL);
if (output != NULL) {
// Remove Newline
size_t length = strlen(output);
if (length > 0 && output[length - 1] == '\n') {
output[length - 1] = '\0';
}
// Print
DEBUG("%s: %s", prefix, output);
free(output);
}
if (!is_exit_status_success(status)) {
ERR("Unable To Gather Debug Information");
}
}
static void print_debug_information() {
// System Information
const char *const command[] = {"uname", "-a", NULL};
run_debug_command(command, "System Information");
// Version
DEBUG("Reborn Version: v%s", MCPI_VERSION);
// Architecture
const char *arch = "Unknown";
#ifdef __x86_64__
arch = "AMD64";
#elif defined(__aarch64__)
arch = "ARM64";
#elif defined(__arm__)
arch = "ARM32";
#endif
DEBUG("Reborn Target Architecture: %s", arch);
}
// Pre-Bootstrap
void pre_bootstrap(int argc, char *argv[]) {
// Set Debug Tag
@ -181,19 +229,20 @@ void pre_bootstrap(int argc, char *argv[]) {
act_sigterm.sa_flags = SA_RESTART;
act_sigterm.sa_handler = &exit_handler;
sigaction(SIGTERM, &act_sigterm, NULL);
// Check Page Size (Not Needed When Using QEMU)
#ifndef USE_QEMU
long page_size = sysconf(_SC_PAGESIZE);
if (page_size != REQUIRED_PAGE_SIZE) {
ERR("Invalid page size! A page size of %ld bytes is required, but the system size is %ld bytes.", (long) REQUIRED_PAGE_SIZE, page_size);
}
#endif
// Debug Information
print_debug_information();
}
// Copy SDK Into ~/.minecraft-pi
void run_simple_command(const char *const command[], const char *error) {
int status = 0;
char *output = run_command(command, &status);
if (output != NULL) {
free(output);
}
if (!is_exit_status_success(status)) {
ERR("%s", error);
}
}
#define HOME_SUBDIRECTORY_FOR_SDK HOME_SUBDIRECTORY_FOR_GAME_DATA "/sdk"
static void copy_sdk(char *binary_directory) {
// Ensure SDK Directory
@ -249,6 +298,7 @@ void bootstrap(int argc, char *argv[]) {
// Get Binary Directory
char *binary_directory = get_binary_directory();
DEBUG("Binary Directory: %s", binary_directory);
// Copy SDK
copy_sdk(binary_directory);
@ -370,23 +420,6 @@ void bootstrap(int argc, char *argv[]) {
set_and_print_env("MCPI_ARM_LD_LIBRARY_PATH", mcpi_ld_path);
free(mcpi_ld_path);
}
// Setup iconv
{
// Native Components
char *host_gconv_path = getenv("GCONV_PATH");
set_and_print_env("MCPI_NATIVE_GCONV_PATH", host_gconv_path);
// ARM Components
#ifdef MCPI_USE_PREBUILT_ARMHF_TOOLCHAIN
char *gconv_path = NULL;
safe_asprintf(&gconv_path, "%s/sysroot/usr/lib/gconv", binary_directory);
set_and_print_env("MCPI_ARM_GCONV_PATH", gconv_path);
free(gconv_path);
#else
set_and_print_env("MCPI_ARM_GCONV_PATH", host_gconv_path);
#endif
}
}
// Configure Preloaded Objects
@ -460,16 +493,18 @@ void bootstrap(int argc, char *argv[]) {
new_args[argv_start] = new_mcpi_exe_path;
// Non-ARM Systems Need QEMU
#ifndef __ARM_ARCH
#ifdef USE_QEMU
argv_start--;
new_args[argv_start] = QEMU_BINARY;
// Use 4k Page Size
set_and_print_env("QEMU_PAGESIZE", STR(REQUIRED_PAGE_SIZE));
#endif
// Setup Environment
setup_exec_environment(1);
// Pass LD_* Variables Through QEMU
#ifndef __ARM_ARCH
#ifdef USE_QEMU
char *qemu_set_env = NULL;
#define pass_variable_through_qemu(name) string_append(&qemu_set_env, "%s%s=%s", qemu_set_env == NULL ? "" : ",", name, getenv(name));
for_each_special_environmental_variable(pass_variable_through_qemu);

View File

@ -4,8 +4,6 @@
extern "C" {
#endif
void run_simple_command(const char *const command[], const char *error);
void pre_bootstrap(int argc, char *argv[]);
void bootstrap(int argc, char *argv[]);

View File

@ -44,3 +44,9 @@ FALSE Force EGL
TRUE Improved Classic Title Screen
FALSE Disable Speed Bridging
FALSE Disable Creative Mode Mining Delay
FALSE Add Biome Colors To Grass
TRUE Generate Caves
FALSE Disable Block Tinting
TRUE Disable Hostile AI In Creative Mode
TRUE Load Custom Skins
TRUE 3D Chest Model

View File

@ -9,6 +9,7 @@
#include <libreborn/libreborn.h>
#include "../util.h"
#include "../bootstrap.h"
#include "launcher.h"
#include "cache.h"
@ -85,7 +86,7 @@ static void run_command_and_set_env(const char *env_name, const char *command[])
if (getenv(env_name) == NULL) {
// Run
int return_code;
char *output = run_command(command, &return_code);
char *output = run_command(command, &return_code, NULL);
if (output != NULL) {
// Trim
int length = strlen(output);

39
launcher/src/util.c Normal file
View File

@ -0,0 +1,39 @@
#include <libreborn/libreborn.h>
#include "util.h"
// Simpler Version Of run_command()
void run_simple_command(const char *const command[], const char *error) {
int status = 0;
char *output = run_command(command, &status, NULL);
if (output != NULL) {
free(output);
}
if (!is_exit_status_success(status)) {
ERR("%s", error);
}
}
// Chop Off Last Component
void chop_last_component(char **str) {
size_t length = strlen(*str);
for (size_t i = 0; i < length; i++) {
size_t j = length - i - 1;
if ((*str)[j] == '/') {
(*str)[j] = '\0';
break;
}
}
}
// Get Binary Directory (Remember To Free)
char *get_binary_directory() {
// Get Path To Current Executable
char *exe = realpath("/proc/self/exe", NULL);
ALLOC_CHECK(exe);
// Chop Off Last Component
chop_last_component(&exe);
// Return
return exe;
}

14
launcher/src/util.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void run_simple_command(const char *const command[], const char *error);
void chop_last_component(char **str);
char *get_binary_directory();
#ifdef __cplusplus
}
#endif

View File

@ -5,7 +5,7 @@ file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include/libreborn")
configure_file(include/libreborn/config.h.in "${CMAKE_CURRENT_BINARY_DIR}/include/libreborn/config.h" ESCAPE_QUOTES @ONLY)
# Util
add_library(reborn-util SHARED src/util/elf.c src/util/exec.c src/util/string.c src/util/util.c src/util/log.c)
add_library(reborn-util SHARED src/util/elf.c src/util/exec.c src/util/string.c src/util/util.c src/util/log.c src/util/cp437.cpp)
target_include_directories(
reborn-util
PUBLIC
@ -13,6 +13,7 @@ target_include_directories(
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>"
"$<INSTALL_INTERFACE:${MCPI_SDK_INCLUDE_DIR}/libreborn>"
)
target_link_libraries(reborn-util PRIVATE utf8cpp)
# Install
install(TARGETS reborn-util DESTINATION "${MCPI_LIB_DIR}")
# SDK
@ -32,3 +33,17 @@ if(BUILD_ARM_COMPONENTS)
# SDK
install(TARGETS reborn-patch EXPORT sdk DESTINATION "${MCPI_SDK_LIB_DIR}")
endif()
# Fake LibPNG To Satisy Symbol Versioning Requirement
if(BUILD_ARM_COMPONENTS)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/fake-libpng.c" "")
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/fake-libpng.vers" "PNG12_0 { global: *; };")
add_library(fake-libpng SHARED "${CMAKE_CURRENT_BINARY_DIR}/fake-libpng.c")
set_target_properties(fake-libpng PROPERTIES
OUTPUT_NAME "png12"
SOVERSION 0
LINK_OPTIONS "LINKER:--version-script=${CMAKE_CURRENT_BINARY_DIR}/fake-libpng.vers"
)
# Install
install(TARGETS fake-libpng DESTINATION "${MCPI_LIB_DIR}")
endif()

View File

@ -11,3 +11,4 @@
#cmakedefine MCPI_VERSION "@MCPI_VERSION@"
#cmakedefine MCPI_VARIANT_NAME "@MCPI_VARIANT_NAME@"
#cmakedefine MCPI_SDK_DIR "@MCPI_SDK_DIR@"
#cmakedefine MCPI_SKIN_SERVER "@MCPI_SKIN_SERVER@"

View File

@ -22,21 +22,15 @@ void set_and_print_env(const char *name, const char *value);
// Safe execvpe()
#define for_each_special_environmental_variable(handle) \
handle("LD_LIBRARY_PATH"); \
handle("GCONV_PATH"); \
handle("LD_PRELOAD");
void setup_exec_environment(int is_arm_component);
__attribute__((noreturn)) void safe_execvpe(const char *const argv[], const char *const envp[]);
// Chop Off Last Component
void chop_last_component(char **str);
// Get Binary Directory (Remember To Free)
char *get_binary_directory();
// Debug Tag
#define CHILD_PROCESS_TAG "(Child Process) "
// Run Command And Get Output
char *run_command(const char *const command[], int *exit_status);
char *run_command(const char *const command[], int *exit_status, size_t *output_size);
#define is_exit_status_success(status) (WIFEXITED(status) && WEXITSTATUS(status) == 0)
// Get Exit Status String

View File

@ -3,7 +3,6 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <iconv.h>
#include <stdint.h>
#include "util.h"
@ -36,7 +35,6 @@ extern "C" {
void sanitize_string(char **str, int max_length, unsigned int allow_newlines);
// CP437
void safe_iconv(iconv_t cd, char *input, size_t input_size, char *output, size_t output_size);
char *to_cp437(const char *input);
char *from_cp437(const char *input);

View File

@ -45,6 +45,11 @@ int is_progress_difference_significant(int32_t new_val, int32_t old_val);
int lock_file(const char *file);
void unlock_file(const char *file, int fd);
// Access Configuration At Runtime
const char *reborn_get_version();
int reborn_is_headless();
int reborn_is_server();
#ifdef __cplusplus
}
#endif

View File

@ -18,6 +18,10 @@
// Generate A BL Instruction
static uint32_t generate_bl_instruction(void *from, void *to, int use_b_instruction) {
if (abs(((int32_t) to) - ((int32_t) from)) > 32000000) {
IMPOSSIBLE();
}
uint32_t instruction;
unsigned char *instruction_array = (unsigned char *) &instruction;
@ -140,11 +144,8 @@ void _overwrite_calls(const char *file, int line, void *start, void *target) {
void *extract_from_bl_instruction(unsigned char *from) {
unsigned char *pc = ((unsigned char *) from) + 8;
int32_t target = 0;
unsigned char *target_array = (unsigned char *) &target;
target_array[0] = from[0];
target_array[1] = from[1];
target_array[2] = from[2];
int32_t target = *(int32_t *) from;
target = (target << 8) >> 8;
int32_t offset = target << 2;

View File

@ -0,0 +1,91 @@
#include <cstdint>
#include <cstdlib>
#include "utf8.h"
#include <libreborn/string.h>
// Conversion Functions
static std::u32string to_utf32(const std::string &s) {
return utf8::utf8to32(s);
}
static std::string to_utf8(const std::u32string &s) {
return utf8::utf32to8(s);
}
// Minecraft-Flavored CP437
#define CP437_CHARACTERS 256
static const std::string cp437_characters_map[CP437_CHARACTERS] = {
"\0", "", "", "", "", "", "", "", "", "", "\n", "", "", "\r", "", "",
"", "", "", "", "", "§", "", "", "", "", "", "", "", "", "", "",
" ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?",
"@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
"P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_",
"`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", "",
"Ç", "ü", "é", "â", "ä", "à", "å", "ç", "ê", "ë", "è", "ï", "î", "ì", "Ä", "Å",
"É", "æ", "Æ", "ô", "ö", "ò", "û", "ù", "ÿ", "Ö", "Ü", "¢", "£", "¥", "", "ƒ",
"á", "í", "ó", "ú", "ñ", "Ñ", "ª", "º", "¿", "", "¬", "½", "¼", "¡", "«", "»",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"α", "ß", "Γ", "π", "Σ", "σ", "µ", "τ", "Φ", "Θ", "Ω", "δ", "", "φ", "ε", "",
"", "±", "", "", "", "", "÷", "", "°", "", "·", "", "", "²", "", "©"
};
static uint32_t *get_cp437_characters_codepoint_map() {
static uint32_t map[CP437_CHARACTERS];
static int is_setup = 0;
if (!is_setup) {
// Build Map
for (int i = 0; i < CP437_CHARACTERS; i++) {
// Convert to UTF-32, Then Extract Codepoint
std::u32string str = to_utf32(cp437_characters_map[i]);
// Extract
map[i] = str[0];
}
is_setup = 1;
}
return map;
}
char *to_cp437(const char *input) {
// Convert To UTF-32 For Easier Parsing
std::u32string utf32_str = to_utf32(input);
// Allocate String
std::string cp437_str;
// Handle Characters
for (size_t i = 0; i < utf32_str.length(); i++) {
uint32_t codepoint = utf32_str[i];
bool valid = false;
for (int j = 0; j < CP437_CHARACTERS; j++) {
uint32_t test_codepoint = get_cp437_characters_codepoint_map()[j];
if (codepoint == test_codepoint) {
valid = true;
cp437_str += j;
break;
}
}
if (!valid) {
cp437_str += '?';
}
}
// Return
return strdup(cp437_str.c_str());
}
char *from_cp437(const char *raw_input) {
// Convert To UTF-32 For Easier Parsing
std::string input = raw_input;
std::u32string utf32_str;
// Handle Characters
for (size_t i = 0; i < input.length(); i++) {
unsigned char c = (unsigned char) input[i];
utf32_str += get_cp437_characters_codepoint_map()[(uint32_t) c];
}
// Convert To UTF-8
return strdup(to_utf8(utf32_str).c_str());
}

View File

@ -43,32 +43,8 @@ __attribute__((noreturn)) void safe_execvpe(const char *const argv[], const char
}
}
// Chop Off Last Component
void chop_last_component(char **str) {
size_t length = strlen(*str);
for (size_t i = 0; i < length; i++) {
size_t j = length - i - 1;
if ((*str)[j] == '/') {
(*str)[j] = '\0';
break;
}
}
}
// Get Binary Directory (Remember To Free)
char *get_binary_directory() {
// Get Path To Current Executable
char *exe = realpath("/proc/self/exe", NULL);
ALLOC_CHECK(exe);
// Chop Off Last Component
chop_last_component(&exe);
// Return
return exe;
}
// Run Command And Get Output
char *run_command(const char *const command[], int *exit_status) {
char *run_command(const char *const command[], int *exit_status, size_t *output_size) {
// Store Output
int output_pipe[2];
safe_pipe2(output_pipe, 0);
@ -107,16 +83,55 @@ char *run_command(const char *const command[], int *exit_status) {
// Read stdout
close(output_pipe[1]);
char *output = NULL;
#define BUFFER_SIZE 1024
size_t size = BUFFER_SIZE;
char *output = malloc(size);
char buf[BUFFER_SIZE];
size_t position = 0;
ssize_t bytes_read = 0;
while ((bytes_read = read(output_pipe[0], (void *) buf, BUFFER_SIZE - 1 /* Account For NULL-Terminator */)) > 0) {
buf[bytes_read] = '\0';
string_append(&output, "%s", buf);
while ((bytes_read = read(output_pipe[0], (void *) buf, BUFFER_SIZE)) > 0) {
// Grow Output If Needed
size_t needed_size = position + bytes_read;
if (needed_size > size) {
// More Memeory Needed
size_t new_size = size;
while (new_size < needed_size) {
new_size += BUFFER_SIZE;
}
char *new_output = realloc(output, new_size);
if (new_output == NULL) {
ERR("Unable To Grow Output Buffer");
} else {
output = new_output;
size = new_size;
}
}
// Copy Data To Output
memcpy(output + position, buf, bytes_read);
position += bytes_read;
}
close(output_pipe[0]);
// Add NULL-Terminator To Output
size_t needed_size = position + 1;
if (needed_size > size) {
// More Memeory Needed
size_t new_size = size + 1;
char *new_output = realloc(output, new_size);
if (new_output == NULL) {
ERR("Unable To Grow Output Buffer (For NULL-Terminator)");
} else {
output = new_output;
}
}
output[position++] = '\0';
// Return Output Size
if (output_size != NULL) {
*output_size = position;
}
// Get Return Code
int status;
waitpid(ret, &status, 0);

View File

@ -1,4 +1,3 @@
#include <iconv.h>
#include <stdint.h>
#include <libreborn/string.h>
@ -23,125 +22,6 @@ void sanitize_string(char **str, int max_length, unsigned int allow_newlines) {
}
}
// Minecraft-Flavored CP437
void safe_iconv(iconv_t cd, char *input, size_t input_size, char *output, size_t output_size) {
iconv(cd, &input, &input_size, &output, &output_size);
}
#define CP437_CHARACTERS 256
static const char *cp437_characters_map[CP437_CHARACTERS] = {
"\0", "", "", "", "", "", "", "", "", "", "\n", "", "", "\r", "", "",
"", "", "", "", "", "§", "", "", "", "", "", "", "", "", "", "",
" ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?",
"@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
"P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_",
"`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", "",
"Ç", "ü", "é", "â", "ä", "à", "å", "ç", "ê", "ë", "è", "ï", "î", "ì", "Ä", "Å",
"É", "æ", "Æ", "ô", "ö", "ò", "û", "ù", "ÿ", "Ö", "Ü", "¢", "£", "¥", "", "ƒ",
"á", "í", "ó", "ú", "ñ", "Ñ", "ª", "º", "¿", "", "¬", "½", "¼", "¡", "«", "»",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"α", "ß", "Γ", "π", "Σ", "σ", "µ", "τ", "Φ", "Θ", "Ω", "δ", "", "φ", "ε", "",
"", "±", "", "", "", "", "÷", "", "°", "", "·", "", "", "²", "", "©"
};
static uint32_t *get_cp437_characters_codepoint_map() {
static uint32_t map[CP437_CHARACTERS];
static int is_setup = 0;
if (!is_setup) {
// Build Map
iconv_t cd = iconv_open("UTF-32LE", "UTF-8");
if (cd != (iconv_t) -1) {
size_t str_size = 4;
uint32_t *str = (uint32_t *) malloc(str_size);
ALLOC_CHECK(str);
for (int i = 0; i < CP437_CHARACTERS; i++) {
// Convert to UTF-32, Then Extract Codepoint
safe_iconv(cd, (char *) cp437_characters_map[i], strlen(cp437_characters_map[i]), (char *) str, str_size);
// Extract
map[i] = str[0];
}
// Free
free(str);
iconv_close(cd);
} else {
IMPOSSIBLE();
}
is_setup = 1;
}
return map;
}
char *to_cp437(const char *input) {
// Convert To UTF-32 For Easier Parsing
size_t in_size = strlen(input);
size_t utf32_str_size = in_size * 4;
size_t real_utf32_str_size = utf32_str_size + 4 /* NULL-terminator */;
uint32_t *utf32_str = (uint32_t *) malloc(real_utf32_str_size);
ALLOC_CHECK(utf32_str);
memset(utf32_str, 0, real_utf32_str_size);
iconv_t cd = iconv_open("UTF-32LE", "UTF-8");
if (cd != (iconv_t) -1) {
safe_iconv(cd, (char *) input, in_size, (char *) utf32_str, utf32_str_size);
iconv_close(cd);
} else {
IMPOSSIBLE();
}
// Allocate String
size_t cp437_str_size;
for (cp437_str_size = 0; utf32_str[cp437_str_size] != 0; cp437_str_size++);
size_t real_cp437_str_size = cp437_str_size + 1 /* NULL-terminator */;
char *cp437_str = (char *) malloc(real_cp437_str_size);
ALLOC_CHECK(cp437_str);
memset(cp437_str, 0, real_cp437_str_size);
// Handle Characters
for (size_t i = 0; utf32_str[i] != 0; i++) {
uint32_t codepoint = utf32_str[i];
for (int j = 0; j < CP437_CHARACTERS; j++) {
uint32_t test_codepoint = get_cp437_characters_codepoint_map()[j];
if (codepoint == test_codepoint) {
cp437_str[i] = j;
break;
}
}
if (cp437_str[i] == '\0') {
cp437_str[i] = '?';
}
}
// Free
free(utf32_str);
// Return
return cp437_str;
}
char *from_cp437(const char *input) {
// Convert To UTF-32 For Easier Parsing
size_t in_size = strlen(input);
size_t utf32_str_size = in_size * 4;
size_t real_utf32_str_size = utf32_str_size + 4 /* NULL-terminator */;
uint32_t *utf32_str = (uint32_t *) malloc(real_utf32_str_size);
ALLOC_CHECK(utf32_str);
memset(utf32_str, 0, real_utf32_str_size);
// Handle Characters
for (size_t i = 0; input[i] != '\0'; i++) {
utf32_str[i] = get_cp437_characters_codepoint_map()[(uint32_t) input[i]];
}
// Convert To UTF-8
size_t out_size = utf32_str_size;
size_t real_out_size = utf32_str_size + 1 /* NULL-terminator */;
char *output = (char *) malloc(real_out_size);
ALLOC_CHECK(output);
memset(output, 0, real_out_size);
iconv_t cd = iconv_open("UTF-8", "UTF-32LE");
if (cd != (iconv_t) -1) {
safe_iconv(cd, (char *) utf32_str, utf32_str_size, output, out_size);
iconv_close(cd);
} else {
IMPOSSIBLE();
}
// Return
return output;
}
// Starts With
int starts_with(const char *str, const char *prefix) {
return strncmp(prefix, str, strlen(prefix)) == 0;

View File

@ -2,6 +2,7 @@
#include <sys/file.h>
#include <libreborn/util.h>
#include <libreborn/config.h>
// Safe Version Of pipe()
void safe_pipe2(int pipefd[2], int flags) {
@ -43,3 +44,22 @@ void unlock_file(const char *file, int fd) {
}
close(fd);
}
// Access Configuration At Runtime
const char *reborn_get_version() {
return MCPI_VERSION;
}
int reborn_is_headless() {
#ifdef MCPI_HEADLESS_MODE
return 1;
#else
return 0;
#endif
}
int reborn_is_server() {
#ifdef MCPI_SERVER_MODE
return 1;
#else
return 0;
#endif
}

View File

@ -11,18 +11,29 @@ target_include_directories(
# SDK
if(BUILD_ARM_COMPONENTS)
install(TARGETS media-layer-headers EXPORT sdk DESTINATION "${MCPI_SDK_LIB_DIR}")
install(DIRECTORY "include/" DESTINATION "${MCPI_SDK_INCLUDE_DIR}/media-layer")
# Copy Headers
set(GLES_HEADERS "include/GLES")
install(
DIRECTORY "include/"
DESTINATION "${MCPI_SDK_INCLUDE_DIR}/media-layer"
PATTERN "${GLES_HEADERS}" EXCLUDE
)
get_filename_component(GLES_HEADERS "${GLES_HEADERS}" REALPATH)
install(
DIRECTORY "${GLES_HEADERS}/"
DESTINATION "${MCPI_SDK_INCLUDE_DIR}/media-layer/GLES"
)
endif()
# Add Extras
add_subdirectory(extras)
# Add Core
if((BUILD_NATIVE_COMPONENTS AND MCPI_USE_MEDIA_LAYER_PROXY) OR (BUILD_ARM_COMPONENTS AND NOT MCPI_USE_MEDIA_LAYER_PROXY))
if(BUILD_MEDIA_LAYER_CORE)
add_subdirectory(core)
endif()
# Add Proxy
if(MCPI_USE_MEDIA_LAYER_PROXY)
if(MCPI_USE_MEDIA_LAYER_PROXY OR BUILD_ARM_COMPONENTS)
add_subdirectory(proxy)
endif()

View File

@ -1,8 +1,5 @@
project(media-layer-core)
# Dependencies
add_subdirectory(dependencies)
# OpenGL
add_subdirectory(gles)
@ -15,18 +12,19 @@ else()
endif()
# Build
add_library(media-layer-core SHARED ${CORE_SRC}) # Dependencies Are Setup Later
# Install
install(TARGETS media-layer-core DESTINATION "${MCPI_LIB_DIR}")
if(BUILD_ARM_COMPONENTS)
install(TARGETS media-layer-core EXPORT sdk DESTINATION "${MCPI_SDK_LIB_DIR}")
add_library(media-layer-core-real SHARED ${CORE_SRC}) # Dependencies Are Setup Later
set_target_properties(media-layer-core-real PROPERTIES OUTPUT_NAME "media-layer-core")
if(BUILD_NATIVE_COMPONENTS)
add_library(media-layer-core ALIAS media-layer-core-real)
endif()
# Install
install(TARGETS media-layer-core-real DESTINATION "${MCPI_LIB_DIR}")
# Link
target_link_libraries(media-layer-core PUBLIC media-layer-headers PUBLIC reborn-util PUBLIC GLESv1_CM PUBLIC dl)
target_link_libraries(media-layer-core-real PUBLIC media-layer-headers PUBLIC reborn-util PUBLIC GLESv1_CM PUBLIC dl)
if(NOT MCPI_HEADLESS_MODE)
# OpenAL
find_library(OPENAL_LIBRARY NAMES openal REQUIRED)
# Link
target_link_libraries(media-layer-core PRIVATE "${OPENAL_LIBRARY}" PRIVATE m PRIVATE glfw PRIVATE LIB_LIEF)
target_link_libraries(media-layer-core-real PRIVATE "${OPENAL_LIBRARY}" PRIVATE m PRIVATE glfw PRIVATE LIB_LIEF)
endif()

View File

@ -1,6 +0,0 @@
project(media-layer-core-dependencies)
# GLFW
if(NOT MCPI_HEADLESS_MODE)
add_subdirectory(glfw)
endif()

@ -1 +0,0 @@
Subproject commit dd8a678a66f1967372e5a5e3deac41ebf65ee127

View File

@ -3,30 +3,14 @@ project(media-layer-gles)
# Build
if(MCPI_HEADLESS_MODE)
# Stubs For Headless Mode
set(GLES_SRC src/stubs.c)
add_library(GLESv1_CM OBJECT src/stubs.c)
target_link_libraries(GLESv1_CM PRIVATE media-layer-headers)
elseif(MCPI_USE_GLES1_COMPATIBILITY_LAYER)
# GLESv1_CM Compatibility Layer
set(GLES_SRC src/compatibility-layer/state.c src/compatibility-layer/passthrough.c src/compatibility-layer/matrix.c src/compatibility-layer/draw.c)
add_library(GLESv1_CM INTERFACE)
target_link_libraries(GLESv1_CM INTERFACE gles-compatibility-layer)
else()
# Passthrough To glfwGetProcAddress()
set(GLES_SRC src/passthrough.c)
endif()
add_library(GLESv1_CM OBJECT ${GLES_SRC})
if(NOT MCPI_HEADLESS_MODE)
target_link_libraries(GLESv1_CM PRIVATE glfw PUBLIC reborn-util PRIVATE dl PRIVATE m)
# Shaders
if(MCPI_USE_GLES1_COMPATIBILITY_LAYER)
embed_resource(GLESv1_CM src/compatibility-layer/shaders/main.vsh)
embed_resource(GLESv1_CM src/compatibility-layer/shaders/main.fsh)
endif()
endif()
# Common
target_link_libraries(GLESv1_CM PUBLIC media-layer-headers)
set_target_properties(GLESv1_CM PROPERTIES SOVERSION "1")
# Install
install(TARGETS GLESv1_CM DESTINATION "${MCPI_LIB_DIR}")
# SDK
if(BUILD_ARM_COMPONENTS)
install(TARGETS GLESv1_CM EXPORT sdk DESTINATION "${MCPI_SDK_LIB_DIR}")
add_library(GLESv1_CM OBJECT src/passthrough.c)
target_link_libraries(GLESv1_CM PRIVATE glfw media-layer-headers)
endif()

View File

@ -1,204 +0,0 @@
#include "state.h"
#include "../passthrough.h"
#include <GLES/gl.h>
#include <libreborn/libreborn.h>
// Shaders
#define REAL_GL_FRAGMENT_SHADER 0x8b30
#define REAL_GL_VERTEX_SHADER 0x8b31
#define REAL_GL_INFO_LOG_LENGTH 0x8b84
#define REAL_GL_COMPILE_STATUS 0x8b81
GL_FUNC(glUseProgram, void, (GLuint program));
GL_FUNC(glGetUniformLocation, GLint, (GLuint program, const GLchar *name));
GL_FUNC(glUniformMatrix4fv, void, (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value));
GL_FUNC(glUniform1i, void, (GLint location, GLint v0));
GL_FUNC(glUniform1f, void, (GLint location, GLfloat v0));
GL_FUNC(glUniform4f, void, (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3));
GL_FUNC(glGetAttribLocation, GLint, (GLuint program, const GLchar *name));
GL_FUNC(glEnableVertexAttribArray, void, (GLuint index));
GL_FUNC(glDisableVertexAttribArray, void, (GLuint index));
GL_FUNC(glVertexAttribPointer, void, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer));
GL_FUNC(glVertexAttrib3f, void, (GLuint index, GLfloat v0, GLfloat v1, GLfloat v2));
GL_FUNC(glVertexAttrib4f, void, (GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3));
GL_FUNC(glCreateShader, GLuint, (GLenum type));
GL_FUNC(glShaderSource, void, (GLuint shader, GLsizei count, const GLchar *const *string, const GLint *length));
GL_FUNC(glCompileShader, void, (GLuint shader));
GL_FUNC(glCreateProgram, GLuint, ());
GL_FUNC(glAttachShader, void, (GLuint program, GLuint shader));
GL_FUNC(glLinkProgram, void, (GLuint program));
GL_FUNC(glGetShaderiv, void, (GLuint shader, GLenum pname, GLint *params));
GL_FUNC(glGetShaderInfoLog, void, (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog));
// Compile Shader
static void log_shader(GLuint shader, const char *name) {
// Log
GLint log_length = 0;
real_glGetShaderiv()(shader, REAL_GL_INFO_LOG_LENGTH, &log_length);
GLchar *log = malloc(log_length * sizeof (GLchar));
ALLOC_CHECK(log);
real_glGetShaderInfoLog()(shader, log_length, &log_length, log);
if (log_length > 0) {
if (log_length > 1 && log[log_length - 1] == '\n') {
log[log_length - 1] = '\0';
}
DEBUG("%s Shader Compile Log: %s", name, log);
}
free(log);
// Check Status
GLint is_compiled = 0;
real_glGetShaderiv()(shader, REAL_GL_COMPILE_STATUS, &is_compiled);
if (!is_compiled) {
ERR("Failed To Compile %s Shader", name);
}
}
static GLuint compile_shader(const char *vertex_shader_text, const int vertex_shader_length, const char *fragment_shader_text, const int fragment_shader_length) {
// Vertex Shader
const GLuint vertex_shader = real_glCreateShader()(REAL_GL_VERTEX_SHADER);
real_glShaderSource()(vertex_shader, 1, &vertex_shader_text, &vertex_shader_length);
real_glCompileShader()(vertex_shader);
log_shader(vertex_shader, "Vertex");
// Fragment Shader
const GLuint fragment_shader = real_glCreateShader()(REAL_GL_FRAGMENT_SHADER);
real_glShaderSource()(fragment_shader, 1, &fragment_shader_text, &fragment_shader_length);
real_glCompileShader()(fragment_shader);
log_shader(fragment_shader, "Fragment");
// Link
GLuint program = real_glCreateProgram()();
real_glAttachShader()(program, vertex_shader);
real_glAttachShader()(program, fragment_shader);
real_glLinkProgram()(program);
// Return
return program;
}
// Shader
extern unsigned char main_vsh[];
extern size_t main_vsh_len;
extern unsigned char main_fsh[];
extern size_t main_fsh_len;
static GLuint get_shader() {
static GLuint program = 0;
if (program == 0) {
program = compile_shader((const char *) main_vsh, main_vsh_len, (const char *) main_fsh, main_fsh_len);
}
return program;
}
// Shader Switching
static void use_shader(GLuint program) {
static GLuint current_program = 0;
if (current_program != program) {
real_glUseProgram()(program);
current_program = program;
}
}
// Array Pointer Drawing
GL_FUNC(glDrawArrays, void, (GLenum mode, GLint first, GLsizei count));
#define lazy_uniform(name) \
static GLint name##_handle = -1; \
if (name##_handle == -1) { \
name##_handle = real_glGetUniformLocation()(program, #name); \
}
void glDrawArrays(GLenum mode, GLint first, GLsizei count) {
// Verify
if (gl_state.array_pointers.vertex.size != 3 || !gl_state.array_pointers.vertex.enabled || gl_state.array_pointers.vertex.type != GL_FLOAT) {
ERR("Unsupported Vertex Conifguration");
}
// Check Mode
int use_color_pointer = gl_state.array_pointers.color.enabled;
if (use_color_pointer && (gl_state.array_pointers.color.size != 4 || gl_state.array_pointers.color.type != GL_UNSIGNED_BYTE)) {
ERR("Unsupported Color Conifguration");
}
int use_texture = gl_state.texture_2d && gl_state.array_pointers.tex_coord.enabled;
if (use_texture && (gl_state.array_pointers.tex_coord.size != 2 || gl_state.array_pointers.tex_coord.type != GL_FLOAT)) {
ERR("Unsupported Texture Conifguration");
}
// Load Shader
GLuint program = get_shader();
use_shader(program);
// Projection Matrix
lazy_uniform(u_projection);
matrix_t *p = &gl_state.matrix_stacks.projection.stack[gl_state.matrix_stacks.projection.i];
real_glUniformMatrix4fv()(u_projection_handle, 1, 0, (GLfloat *) &p->data[0][0]);
// Model View Matrix
lazy_uniform(u_model_view);
p = &gl_state.matrix_stacks.model_view.stack[gl_state.matrix_stacks.model_view.i];
real_glUniformMatrix4fv()(u_model_view_handle, 1, 0, (GLfloat *) &p->data[0][0]);
// Has Texture
lazy_uniform(u_has_texture); \
real_glUniform1i()(u_has_texture_handle, use_texture); \
// Texture Matrix
lazy_uniform(u_texture);
p = &gl_state.matrix_stacks.texture.stack[gl_state.matrix_stacks.texture.i];
real_glUniformMatrix4fv()(u_texture_handle, 1, 0, (GLfloat *) &p->data[0][0]);
// Texture Unit
lazy_uniform(u_texture_unit);
real_glUniform1i()(u_texture_unit_handle, 0);
// Alpha Test
lazy_uniform(u_alpha_test);
real_glUniform1i()(u_alpha_test_handle, gl_state.alpha_test);
// Color
GLint a_color_handle = real_glGetAttribLocation()(program, "a_color");
if (use_color_pointer) {
real_glVertexAttribPointer()(a_color_handle, gl_state.array_pointers.color.size, gl_state.array_pointers.color.type, 1, gl_state.array_pointers.color.stride, gl_state.array_pointers.color.pointer);
real_glEnableVertexAttribArray()(a_color_handle);
} else {
real_glVertexAttrib4f()(a_color_handle, gl_state.color.red, gl_state.color.green, gl_state.color.blue, gl_state.color.alpha);
}
// Fog
lazy_uniform(u_fog);
real_glUniform1i()(u_fog_handle, gl_state.fog.enabled);
if (gl_state.fog.enabled) {
lazy_uniform(u_fog_color);
real_glUniform4f()(u_fog_color_handle, gl_state.fog.color[0], gl_state.fog.color[1], gl_state.fog.color[2], gl_state.fog.color[3]);
lazy_uniform(u_fog_is_linear);
real_glUniform1i()(u_fog_is_linear_handle, gl_state.fog.mode == GL_LINEAR);
lazy_uniform(u_fog_start);
real_glUniform1f()(u_fog_start_handle, gl_state.fog.start);
lazy_uniform(u_fog_end);
real_glUniform1f()(u_fog_end_handle, gl_state.fog.end);
}
// Vertices
GLint a_vertex_coords_handle = real_glGetAttribLocation()(program, "a_vertex_coords");
real_glVertexAttribPointer()(a_vertex_coords_handle, gl_state.array_pointers.vertex.size, gl_state.array_pointers.vertex.type, 0, gl_state.array_pointers.vertex.stride, gl_state.array_pointers.vertex.pointer);
real_glEnableVertexAttribArray()(a_vertex_coords_handle);
// Texture Coordinates
GLint a_texture_coords_handle = real_glGetAttribLocation()(program, "a_texture_coords");
if (use_texture) {
real_glVertexAttribPointer()(a_texture_coords_handle, gl_state.array_pointers.tex_coord.size, gl_state.array_pointers.tex_coord.type, 0, gl_state.array_pointers.tex_coord.stride, gl_state.array_pointers.tex_coord.pointer);
real_glEnableVertexAttribArray()(a_texture_coords_handle);
} else {
real_glVertexAttrib3f()(a_texture_coords_handle, 0, 0, 0);
}
// Draw
real_glDrawArrays()(mode, first, count);
// Cleanup
if (use_color_pointer) {
real_glDisableVertexAttribArray()(a_color_handle);
}
real_glDisableVertexAttribArray()(a_vertex_coords_handle);
if (use_texture) {
real_glDisableVertexAttribArray()(a_texture_coords_handle);
}
}

View File

@ -1,131 +0,0 @@
#include <math.h>
#include <string.h>
#include <libreborn/libreborn.h>
#include "state.h"
// Matrix Common
static void matrix_copy(matrix_t *src, matrix_t *dst) {
memcpy((void *) dst->data, (void *) src->data, MATRIX_DATA_SIZE);
}
// Identity Matrix
static matrix_t identity_matrix = {
.data = {
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1}
}
};
static void init_matrix_stack(matrix_stack_t *stack) {
matrix_copy(&identity_matrix, &stack->stack[0]);
}
__attribute__((constructor)) static void init_matrix_stacks() {
init_matrix_stack(&gl_state.matrix_stacks.model_view);
init_matrix_stack(&gl_state.matrix_stacks.projection);
init_matrix_stack(&gl_state.matrix_stacks.texture);
}
// Matrix Mode
static matrix_stack_t *get_matrix_stack() {
switch (gl_state.matrix_stacks.mode) {
case GL_MODELVIEW: {
return &gl_state.matrix_stacks.model_view;
}
case GL_PROJECTION: {
return &gl_state.matrix_stacks.projection;
}
case GL_TEXTURE: {
return &gl_state.matrix_stacks.texture;
}
default: {
ERR("Unsupported Matrix Mode: %i", gl_state.matrix_stacks.mode);
}
}
}
// Matrix Functions
void glMatrixMode(GLenum mode) {
gl_state.matrix_stacks.mode = mode;
}
void glPopMatrix() {
get_matrix_stack()->i--;
}
void glLoadIdentity() {
matrix_stack_t *stack = get_matrix_stack();
matrix_copy(&identity_matrix, &stack->stack[stack->i]);
}
void glPushMatrix() {
matrix_stack_t *stack = get_matrix_stack();
matrix_copy(&stack->stack[stack->i], &stack->stack[stack->i + 1]);
stack->i++;
}
void glMultMatrixf(const GLfloat *m) {
matrix_t new_matrix;
matrix_stack_t *stack = get_matrix_stack();
matrix_t *current_matrix = &stack->stack[stack->i];
for (int x = 0; x < MATRIX_SIZE; x++) {
for (int y = 0; y < MATRIX_SIZE; y++) {
GLfloat result = 0;
for (int i = 0; i < MATRIX_SIZE; i++) {
result += (current_matrix->data[i][y] * m[(x * MATRIX_SIZE) + i]);
}
new_matrix.data[x][y] = result;
}
}
matrix_copy(&new_matrix, current_matrix);
}
void glScalef(GLfloat x, GLfloat y, GLfloat z) {
GLfloat m[] = {
x, 0, 0, 0,
0, y, 0, 0,
0, 0, z, 0,
0, 0, 0, 1
};
glMultMatrixf(m);
}
void glTranslatef(GLfloat x, GLfloat y, GLfloat z) {
GLfloat m[] = {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
x, y, z, 1
};
glMultMatrixf(m);
}
void glOrthof(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat near, GLfloat far) {
GLfloat m[] = {
(2.f / (right - left)), 0, 0, 0,
0, (2.f / (top - bottom)), 0, 0,
0, 0, (-2.f / (far - near)), 0,
-((right + left) / (right - left)), -((top + bottom) / (top - bottom)), -((far + near) / (far - near)), 1
};
glMultMatrixf(m);
}
#define DEG2RAD (M_PI / 180.f)
void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) {
// Normalize
GLfloat length = sqrtf((x * x) + (y * y) + (z * z));
x /= length;
y /= length;
z /= length;
// Values
GLfloat angle_radians = angle * DEG2RAD;
GLfloat c = cosf(angle_radians);
GLfloat s = sinf(angle_radians);
GLfloat x2 = x * x;
GLfloat y2 = y * y;
GLfloat z2 = z * z;
// Multiply
GLfloat m[] = {
x2 * (1.f - c) + c, (x * y) * (1.f - c) + (z * s), (x * z) * (1.f - c) - (y * s), 0,
(x * y) * (1.f - c) - (z * s), y2 * (1.f - c) + c, (y * z) * (1.f - c) + (x * s), 0,
(x * z) * (1.f - c) + (y * s), (y * z) * (1.f - c) - (x * s), z2 * (1.f - c) + c, 0,
0, 0, 0, 1.f
};
glMultMatrixf(m);
}

View File

@ -1,9 +0,0 @@
#include <GLES/gl.h>
// Matrix Common
#define MATRIX_SIZE 4
#define MATRIX_DATA_SIZE (sizeof (GLfloat) * MATRIX_SIZE * MATRIX_SIZE)
// OpenGL Matrices Are Column-Major
typedef struct {
GLfloat data[MATRIX_SIZE][MATRIX_SIZE];
} matrix_t;

View File

@ -1,117 +0,0 @@
#include <GLES/gl.h>
#include "../passthrough.h"
// Simple v1.1 -> v2.0 Passthrough Functions
GL_FUNC(glLineWidth, void, (GLfloat width));
void glLineWidth(GLfloat width) {
real_glLineWidth()(width);
}
GL_FUNC(glBlendFunc, void, (GLenum sfactor, GLenum dfactor));
void glBlendFunc(GLenum sfactor, GLenum dfactor) {
real_glBlendFunc()(sfactor, dfactor);
}
GL_FUNC(glClear, void, (GLbitfield mask));
void glClear(GLbitfield mask) {
real_glClear()(mask);
}
GL_FUNC(glBufferData, void, (GLenum target, GLsizeiptr size, const void *data, GLenum usage));
void glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage) {
real_glBufferData()(target, size, data, usage);
}
GL_FUNC(glScissor, void, (GLint x, GLint y, GLsizei width, GLsizei height));
void glScissor(GLint x, GLint y, GLsizei width, GLsizei height) {
real_glScissor()(x, y, width, height);
}
GL_FUNC(glTexParameteri, void, (GLenum target, GLenum pname, GLint param));
void glTexParameteri(GLenum target, GLenum pname, GLint param) {
real_glTexParameteri()(target, pname, param);
}
GL_FUNC(glTexImage2D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels));
void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels) {
real_glTexImage2D()(target, level, internalformat, width, height, border, format, type, pixels);
}
GL_FUNC(glPolygonOffset, void, (GLfloat factor, GLfloat units));
void glPolygonOffset(GLfloat factor, GLfloat units) {
real_glPolygonOffset()(factor, units);
}
GL_FUNC(glDepthRangef, void, (GLclampf near, GLclampf far));
void glDepthRangef(GLclampf near, GLclampf far) {
real_glDepthRangef()(near, far);
}
GL_FUNC(glBindBuffer, void, (GLenum target, GLuint buffer));
void glBindBuffer(GLenum target, GLuint buffer) {
real_glBindBuffer()(target, buffer);
}
GL_FUNC(glDepthFunc, void, (GLenum func));
void glDepthFunc(GLenum func) {
real_glDepthFunc()(func);
}
GL_FUNC(glClearColor, void, (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha));
void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) {
real_glClearColor()(red, green, blue, alpha);
}
GL_FUNC(glDepthMask, void, (GLboolean flag));
void glDepthMask(GLboolean flag) {
real_glDepthMask()(flag);
}
GL_FUNC(glHint, void, (GLenum target, GLenum mode));
void glHint(GLenum target, GLenum mode) {
if (target != GL_PERSPECTIVE_CORRECTION_HINT) {
real_glHint()(target, mode);
}
}
GL_FUNC(glDeleteBuffers, void, (GLsizei n, const GLuint *buffers));
void glDeleteBuffers(GLsizei n, const GLuint *buffers) {
real_glDeleteBuffers()(n, buffers);
}
GL_FUNC(glColorMask, void, (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha));
void glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) {
real_glColorMask()(red, green, blue, alpha);
}
GL_FUNC(glTexSubImage2D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels));
void glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels) {
real_glTexSubImage2D()(target, level, xoffset, yoffset, width, height, format, type, pixels);
}
GL_FUNC(glGenTextures, void, (GLsizei n, GLuint *textures));
void glGenTextures(GLsizei n, GLuint *textures) {
real_glGenTextures()(n, textures);
}
GL_FUNC(glDeleteTextures, void, (GLsizei n, const GLuint *textures));
void glDeleteTextures(GLsizei n, const GLuint *textures) {
real_glDeleteTextures()(n, textures);
}
GL_FUNC(glBindTexture, void, (GLenum target, GLuint texture));
void glBindTexture(GLenum target, GLuint texture) {
real_glBindTexture()(target, texture);
}
GL_FUNC(glCullFace, void, (GLenum mode));
void glCullFace(GLenum mode) {
real_glCullFace()(mode);
}
GL_FUNC(glViewport, void, (GLint x, GLint y, GLsizei width, GLsizei height));
void glViewport(GLint x, GLint y, GLsizei width, GLsizei height) {
real_glViewport()(x, y, width, height);
}
GL_FUNC(glIsEnabled, GLboolean, (GLenum cap));
GLboolean glIsEnabled(GLenum cap) {
return real_glIsEnabled()(cap);
}
GL_FUNC(glGetIntegerv, void, (GLenum pname, GLint *data));
void glGetIntegerv(GLenum pname, GLint *data) {
real_glGetIntegerv()(pname, data);
}
GL_FUNC(glReadPixels, void, (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *data));
void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *data) {
real_glReadPixels()(x, y, width, height, format, type, data);
}
void glShadeModel(__attribute__((unused)) GLenum mode) {
// Do Nothing
}
void glNormal3f(__attribute__((unused)) GLfloat nx, __attribute__((unused)) GLfloat ny, __attribute__((unused)) GLfloat nz) {
// Do Nothing
}
GL_FUNC(glGenBuffers, void, (GLsizei n, GLuint *buffers));
void glGenBuffers(GLsizei n, GLuint *buffers) {
real_glGenBuffers()(n, buffers);
}

View File

@ -1,39 +0,0 @@
#version 100
precision mediump float;
// Texture
uniform bool u_has_texture;
uniform sampler2D u_texture_unit;
// Color
varying vec4 v_color;
varying vec4 v_texture_pos;
// Alpha Test
uniform bool u_alpha_test;
// Fog
uniform bool u_fog;
uniform vec4 u_fog_color;
uniform bool u_fog_is_linear;
uniform float u_fog_start;
uniform float u_fog_end;
varying vec4 v_fog_eye_position;
// Main
void main(void) {
gl_FragColor = v_color;
// Texture
if (u_has_texture) {
gl_FragColor *= texture2D(u_texture_unit, v_texture_pos.xy);
}
// Alpha Test
if (u_alpha_test && gl_FragColor.a <= 0.1) {
discard;
}
// Fog
if (u_fog) {
float fog_factor;
if (u_fog_is_linear) {
fog_factor = (u_fog_end - length(v_fog_eye_position)) / (u_fog_end - u_fog_start);
} else {
fog_factor = exp(-u_fog_start * length(v_fog_eye_position));
}
gl_FragColor = mix(gl_FragColor, u_fog_color, 1.0 - clamp(fog_factor, 0.0, 1.0));
}
}

View File

@ -1,22 +0,0 @@
#version 100
precision mediump float;
// Matrices
uniform mat4 u_projection;
uniform mat4 u_model_view;
uniform mat4 u_texture;
// Texture
attribute vec3 a_vertex_coords;
attribute vec2 a_texture_coords;
varying vec4 v_texture_pos;
// Color
attribute vec4 a_color;
varying vec4 v_color;
// Fog
varying vec4 v_fog_eye_position;
// Main
void main(void) {
v_texture_pos = u_texture * vec4(a_texture_coords.xy, 0.0, 1.0);
gl_Position = u_projection * u_model_view * vec4(a_vertex_coords.xyz, 1.0);
v_color = a_color;
v_fog_eye_position = u_model_view * vec4(a_vertex_coords.xyz, 1.0);
}

View File

@ -1,178 +0,0 @@
#include <libreborn/libreborn.h>
#include "state.h"
#include "../passthrough.h"
// GL State
gl_state_t gl_state = {
.color = {
.red = 1,
.green = 1,
.blue = 1,
.alpha = 1
},
.matrix_stacks = {
.mode = GL_MODELVIEW
},
.alpha_test = 0,
.texture_2d = 0,
.fog = {
.enabled = 0,
.mode = GL_LINEAR,
.color = {0, 0, 0, 0},
.start = 0,
.end = 1
}
};
// Change Color
void glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {
gl_state.color.red = red;
gl_state.color.green = green;
gl_state.color.blue = blue;
gl_state.color.alpha = alpha;
}
// Array Pointer Storage
#define ARRAY_POINTER_FUNC(func, name) \
void func(GLint size, GLenum type, GLsizei stride, const void *pointer) { \
gl_state.array_pointers.name.size = size; \
gl_state.array_pointers.name.type = type; \
gl_state.array_pointers.name.stride = stride; \
gl_state.array_pointers.name.pointer = pointer; \
}
ARRAY_POINTER_FUNC(glVertexPointer, vertex)
ARRAY_POINTER_FUNC(glColorPointer, color)
ARRAY_POINTER_FUNC(glTexCoordPointer, tex_coord)
static array_pointer_t *get_array_pointer(GLenum array) {
switch (array) {
case GL_VERTEX_ARRAY: {
return &gl_state.array_pointers.vertex;
}
case GL_COLOR_ARRAY: {
return &gl_state.array_pointers.color;
}
case GL_TEXTURE_COORD_ARRAY: {
return &gl_state.array_pointers.tex_coord;
}
default: {
ERR("Unsupported Array Pointer: %i", array);
}
}
}
void glEnableClientState(GLenum array) {
get_array_pointer(array)->enabled = 1;
}
void glDisableClientState(GLenum array) {
get_array_pointer(array)->enabled = 0;
}
// Enable/Disable State
GL_FUNC(glEnable, void, (GLenum cap));
void glEnable(GLenum cap) {
switch (cap) {
case GL_ALPHA_TEST: {
gl_state.alpha_test = 1;
break;
}
case GL_TEXTURE_2D: {
gl_state.texture_2d = 1;
break;
}
case GL_COLOR_MATERIAL: {
// Ignore
break;
}
case GL_FOG: {
gl_state.fog.enabled = 1;
break;
}
default: {
real_glEnable()(cap);
break;
}
}
}
GL_FUNC(glDisable, void, (GLenum cap));
void glDisable(GLenum cap) {
switch (cap) {
case GL_ALPHA_TEST: {
gl_state.alpha_test = 0;
break;
}
case GL_TEXTURE_2D: {
gl_state.texture_2d = 0;
break;
}
case GL_COLOR_MATERIAL: {
// Ignore
break;
}
case GL_FOG: {
gl_state.fog.enabled = 0;
break;
}
default: {
real_glDisable()(cap);
break;
}
}
}
void glAlphaFunc(GLenum func, GLclampf ref) {
if (func != GL_GREATER && ref != 0.1f) {
ERR("Unsupported Alpha Function");
}
}
// Fog
#define UNSUPPORTED_FOG() ERR("Unsupported Fog Configuration")
void glFogfv(GLenum pname, const GLfloat *params) {
if (pname == GL_FOG_COLOR) {
memcpy((void *) gl_state.fog.color, params, sizeof (gl_state.fog.color));
} else {
UNSUPPORTED_FOG();
}
}
void glFogx(GLenum pname, GLfixed param) {
if (pname == GL_FOG_MODE && (param == GL_LINEAR || param == GL_EXP)) {
gl_state.fog.mode = param;
} else {
UNSUPPORTED_FOG();
}
}
void glFogf(GLenum pname, GLfloat param) {
switch (pname) {
case GL_FOG_DENSITY:
case GL_FOG_START: {
gl_state.fog.start = param;
break;
}
case GL_FOG_END: {
gl_state.fog.end = param;
break;
}
default: {
UNSUPPORTED_FOG();
break;
}
}
}
// Get Matrix Data
GL_FUNC(glGetFloatv, void, (GLenum pname, GLfloat *params));
void glGetFloatv(GLenum pname, GLfloat *params) {
switch (pname) {
case GL_MODELVIEW_MATRIX: {
memcpy((void *) params, gl_state.matrix_stacks.model_view.stack[gl_state.matrix_stacks.model_view.i].data, MATRIX_DATA_SIZE);
break;
}
case GL_PROJECTION_MATRIX: {
memcpy((void *) params, gl_state.matrix_stacks.projection.stack[gl_state.matrix_stacks.projection.i].data, MATRIX_DATA_SIZE);
break;
}
default: {
real_glGetFloatv()(pname, params);
break;
}
}
}

View File

@ -1,50 +0,0 @@
#include <GLES/gl.h>
#include "matrix.h"
// Matrix Data
#define MATRIX_STACK_DEPTH 256
typedef struct {
matrix_t stack[MATRIX_STACK_DEPTH];
unsigned int i;
} matrix_stack_t;
// Array Pointer Storage
typedef struct {
GLboolean enabled;
GLint size;
GLenum type;
GLsizei stride;
const void *pointer;
} array_pointer_t;
// GL State
typedef struct {
struct {
GLfloat red;
GLfloat green;
GLfloat blue;
GLfloat alpha;
} color;
struct {
GLenum mode;
matrix_stack_t model_view;
matrix_stack_t projection;
matrix_stack_t texture;
} matrix_stacks;
struct {
array_pointer_t vertex;
array_pointer_t color;
array_pointer_t tex_coord;
} array_pointers;
GLboolean alpha_test;
GLboolean texture_2d;
struct {
GLboolean enabled;
GLfixed mode;
GLfloat color[4];
GLfloat start;
GLfloat end;
} fog;
} gl_state_t;
extern gl_state_t gl_state;

View File

@ -1,6 +1,6 @@
#include <GLES/gl.h>
#include "passthrough.h"
#include "../dependencies/gles-compatibility-layer/src/passthrough.h"
GL_FUNC(glFogfv, void, (GLenum pname, const GLfloat *params));
void glFogfv(GLenum pname, const GLfloat *params) {

View File

@ -1,19 +0,0 @@
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <libreborn/libreborn.h>
// Load GL Function
#define GL_FUNC(name, return_type, args) \
typedef return_type (*real_##name##_t)args; \
\
static real_##name##_t real_##name() { \
static real_##name##_t func = NULL; \
if (!func) { \
func = (real_##name##_t) glfwGetProcAddress(#name); \
if (!func) { \
ERR("Error Resolving GL Symbol: " #name ": %s", dlerror()); \
} \
} \
return func; \
}

View File

@ -140,11 +140,11 @@ void media_audio_play(const char *source, const char *name, float x, float y, fl
// Set Attenuation
alSourcei(al_source, AL_DISTANCE_MODEL, AL_LINEAR_DISTANCE_CLAMPED);
AL_ERROR_CHECK();
alSourcef(al_source, AL_MAX_DISTANCE, 22.0f);
alSourcef(al_source, AL_MAX_DISTANCE, 16.0f);
AL_ERROR_CHECK();
alSourcef(al_source, AL_ROLLOFF_FACTOR, 1.0f);
alSourcef(al_source, AL_ROLLOFF_FACTOR, 6.0f);
AL_ERROR_CHECK();
alSourcef(al_source, AL_REFERENCE_DISTANCE, 2.0f);
alSourcef(al_source, AL_REFERENCE_DISTANCE, 5.0f);
AL_ERROR_CHECK();
// Set Buffer

View File

@ -12,14 +12,14 @@
#include "engine.h"
// Load Symbol From ELF File
static void load_symbol(const char *source, const char *name, std::function<void(unsigned char *, uint32_t)> callback) {
static void load_symbol(const char *source, const char *name, std::function<void(const unsigned char *, uint32_t)> callback) {
static std::unique_ptr<LIEF::ELF::Binary> binary = NULL;
if (binary == NULL) {
binary = LIEF::ELF::Parser::parse(source);
}
const LIEF::ELF::Symbol *symbol = binary->get_dynamic_symbol(name);
if (symbol != NULL) {
std::vector<uint8_t> data = binary->get_content_from_virtual_address(symbol->value(), symbol->size(), LIEF::Binary::VA_TYPES::VA);
LIEF::span<const uint8_t> data = binary->get_content_from_virtual_address(symbol->value(), symbol->size(), LIEF::Binary::VA_TYPES::VA);
callback(data.data(), data.size());
} else {
WARN("Unable To Find Symbol: %s", name);
@ -45,7 +45,7 @@ static ALuint load_sound(const char *source, const char *name) {
ALuint buffer = 0;
// Load Symbol
load_symbol(source, name, [name, &buffer](unsigned char *symbol, uint32_t size) {
load_symbol(source, name, [name, &buffer](const unsigned char *symbol, uint32_t size) {
// Load Metadata
if (size < sizeof (audio_metadata)) {
WARN("Symbol Too Small To Contain Audio Metadata: %s", name);
@ -77,7 +77,7 @@ static ALuint load_sound(const char *source, const char *name) {
WARN("Symbol Too Small To Contain Specified Audio Data: %s", name);
return;
}
unsigned char *data = symbol + sizeof (audio_metadata);
const unsigned char *data = symbol + sizeof (audio_metadata);
// Create Buffer
alGenBuffers(1, &buffer);

View File

@ -55,7 +55,8 @@ static void character_event(char c) {
// SDL_UserEvent Is Never Used In MCPI, So It Is Repurposed For Character Events
SDL_Event event;
event.type = SDL_USEREVENT;
event.user.code = (int) c;
event.user.code = USER_EVENT_CHARACTER;
event.user.data1 = (int) c;
SDL_PushEvent(&event);
}
@ -167,14 +168,21 @@ static SDLMod glfw_modifier_to_sdl_modifier(int mods) {
// Pass Key Presses To SDL
static void glfw_key_raw(int key, int scancode, int action, int mods) {
SDL_Event event;
SDL_Event event1;
int up = action == GLFW_RELEASE;
event.type = up ? SDL_KEYUP : SDL_KEYDOWN;
event.key.state = up ? SDL_RELEASED : SDL_PRESSED;
event.key.keysym.scancode = scancode;
event.key.keysym.mod = glfw_modifier_to_sdl_modifier(mods);
event.key.keysym.sym = glfw_key_to_sdl_key(key);
SDL_PushEvent(&event);
event1.type = up ? SDL_KEYUP : SDL_KEYDOWN;
event1.key.state = up ? SDL_RELEASED : SDL_PRESSED;
event1.key.keysym.scancode = scancode;
event1.key.keysym.mod = glfw_modifier_to_sdl_modifier(mods);
event1.key.keysym.sym = glfw_key_to_sdl_key(key);
SDL_PushEvent(&event1);
// Allow MCPI To Access Original GLFW Keycode
SDL_Event event2;
event2.type = SDL_USEREVENT;
event2.user.code = USER_EVENT_REAL_KEY;
event2.user.data1 = event1.key.state;
event2.user.data2 = key;
SDL_PushEvent(&event2);
}
static void glfw_key(__attribute__((unused)) GLFWwindow *window, int key, int scancode, int action, int mods) {
if (is_interactable) {
@ -606,6 +614,12 @@ void SDL_WM_SetCaption(const char *title, __attribute__((unused)) const char *ic
// Make Window Context Current
glfwMakeContextCurrent(glfw_window);
// Setup Compatibility Layer
#ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER
extern void init_gles_compatibility_layer();
init_gles_compatibility_layer();
#endif
// Debug
glGetString_t glGetString = (glGetString_t) glfwGetProcAddress("glGetString");
@ -679,12 +693,6 @@ void _media_handle_SDL_PollEvent() {
// Process GLFW Events
glfwPollEvents();
// Fix Joystick Detection While Running (Remove When glfw/glfw#2198 Is Merged)
extern void _glfwDetectJoystickConnectionLinux(void);
if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) {
_glfwDetectJoystickConnectionLinux();
}
// Controller
update_controller_state();

1
media-layer/include/GLES Symbolic link
View File

@ -0,0 +1 @@
../../dependencies/gles-compatibility-layer/src/include/GLES

View File

@ -1,114 +0,0 @@
#pragma once
#include <stdio.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define GL_FALSE 0
#define GL_ARRAY_BUFFER_BINDING 0x8894
#define GL_ARRAY_BUFFER 0x8892
#define GL_TEXTURE_BINDING_2D 0x8069
#define GL_UNSIGNED_BYTE 0x1401
#define GL_FLOAT 0x1406
#define GL_RGB 0x1907
#define GL_RGBA 0x1908
#define GL_MODELVIEW_MATRIX 0xba6
#define GL_PROJECTION_MATRIX 0xba7
#define GL_VIEWPORT 0xba2
#define GL_DEPTH_TEST 0xb71
#define GL_PACK_ALIGNMENT 0xd05
#define GL_UNPACK_ALIGNMENT 0xcf5
#define GL_SRC_ALPHA 0x302
#define GL_ONE_MINUS_SRC_ALPHA 0x303
#define GL_MODELVIEW 0x1700
#define GL_PROJECTION 0x1701
#define GL_TEXTURE 0x1702
#define GL_VERTEX_ARRAY 0x8074
#define GL_COLOR_ARRAY 0x8076
#define GL_TEXTURE_COORD_ARRAY 0x8078
#define GL_GREATER 0x204
#define GL_ALPHA_TEST 0xbc0
#define GL_TEXTURE_2D 0xde1
#define GL_COLOR_MATERIAL 0xb57
#define GL_PERSPECTIVE_CORRECTION_HINT 0xc50
#define GL_FOG 0xb60
#define GL_LINEAR 0x2601
#define GL_EXP 0x800
#define GL_FOG_DENSITY 0xb62
#define GL_FOG_START 0xb63
#define GL_FOG_END 0xb64
#define GL_FOG_MODE 0xb65
#define GL_FOG_COLOR 0xb66
#define GL_BLEND 0xbe2
typedef float GLfloat;
typedef float GLclampf;
typedef int GLint;
typedef unsigned char GLboolean;
typedef int GLsizei;
typedef unsigned int GLuint;
typedef ssize_t GLsizeiptr;
typedef intptr_t GLintptr;
typedef int32_t GLfixed;
typedef unsigned int GLbitfield;
typedef unsigned int GLenum;
typedef char GLchar;
void glFogfv(GLenum pname, const GLfloat *params);
void glVertexPointer(GLint size, GLenum type, GLsizei stride, const void *pointer);
void glLineWidth(GLfloat width);
void glBlendFunc(GLenum sfactor, GLenum dfactor);
void glDrawArrays(GLenum mode, GLint first, GLsizei count);
void glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
void glClear(GLbitfield mask);
void glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage);
void glFogx(GLenum pname, GLfixed param);
void glFogf(GLenum pname, GLfloat param);
void glMatrixMode(GLenum mode);
void glColorPointer(GLint size, GLenum type, GLsizei stride, const void *pointer);
void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);
void glTexParameteri(GLenum target, GLenum pname, GLint param);
void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels);
void glEnable(GLenum cap);
void glEnableClientState(GLenum array);
void glPolygonOffset(GLfloat factor, GLfloat units);
void glDisableClientState(GLenum array);
void glDepthRangef(GLclampf near, GLclampf far);
void glDepthFunc(GLenum func);
void glBindBuffer(GLenum target, GLuint buffer);
void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
void glPopMatrix();
void glLoadIdentity();
void glScalef(GLfloat x, GLfloat y, GLfloat z);
void glPushMatrix();
void glDepthMask(GLboolean flag);
void glHint(GLenum target, GLenum mode);
void glMultMatrixf(const GLfloat *m);
void glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const void *pointer);
void glDeleteBuffers(GLsizei n, const GLuint *buffers);
void glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);
void glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels);
void glGenTextures(GLsizei n, GLuint *textures);
void glDeleteTextures(GLsizei n, const GLuint *textures);
void glAlphaFunc(GLenum func, GLclampf ref);
void glGetFloatv(GLenum pname, GLfloat *params);
void glBindTexture(GLenum target, GLuint texture);
void glTranslatef(GLfloat x, GLfloat y, GLfloat z);
void glShadeModel(GLenum mode);
void glOrthof(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat near, GLfloat far);
void glDisable(GLenum cap);
void glCullFace(GLenum mode);
void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
void glViewport(GLint x, GLint y, GLsizei width, GLsizei height);
void glNormal3f(GLfloat nx, GLfloat ny, GLfloat nz);
GLboolean glIsEnabled(GLenum cap);
void glGetIntegerv(GLenum pname, GLint *data);
void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *data);
void glGenBuffers(GLsizei n, GLuint *buffers);
#ifdef __cplusplus
}
#endif

View File

@ -8,6 +8,10 @@ extern "C" {
#define DEFAULT_WIDTH 840
#define DEFAULT_HEIGHT 480
// SDL User Events
#define USER_EVENT_CHARACTER 0 // data1 = 8-Bit Character
#define USER_EVENT_REAL_KEY 1 // data1 = SDL_RELEASED/PRESSED, data2 = GLFW Key Code
void media_ensure_loaded();
void media_toggle_fullscreen();

View File

@ -17,6 +17,8 @@ elseif(BUILD_ARM_COMPONENTS)
target_link_libraries(media-layer-core media-layer-headers reborn-util)
target_compile_definitions(media-layer-core PRIVATE -DMEDIA_LAYER_PROXY_SERVER)
# Install
install(TARGETS media-layer-core DESTINATION "${MCPI_LIB_DIR}")
if(MCPI_USE_MEDIA_LAYER_PROXY)
install(TARGETS media-layer-core DESTINATION "${MCPI_LIB_DIR}")
endif()
install(TARGETS media-layer-core EXPORT sdk DESTINATION "${MCPI_SDK_LIB_DIR}")
endif()

View File

@ -17,10 +17,7 @@ install(DIRECTORY "include/" DESTINATION "${MCPI_SDK_INCLUDE_DIR}/mods")
add_library(compat SHARED src/compat/compat.c src/compat/egl.c src/compat/x11.c src/compat/bcm_host.c)
target_link_libraries(compat mods-headers reborn-patch media-layer-core)
if(NOT MCPI_HEADLESS_MODE)
target_link_libraries(compat screenshot)
endif()
if(NOT MCPI_SERVER_MODE)
target_link_libraries(compat input sign chat home dl)
target_link_libraries(compat screenshot input sign chat home dl)
endif()
add_library(readdir SHARED src/readdir/readdir.c)
@ -33,7 +30,7 @@ target_link_libraries(version mods-headers reborn-patch symbols)
add_library(chat SHARED src/chat/chat.cpp src/chat/ui.c)
target_link_libraries(chat mods-headers reborn-patch symbols feature)
if(NOT MCPI_SERVER_MODE)
if(NOT MCPI_HEADLESS_MODE)
target_link_libraries(chat input media-layer-core pthread)
endif()
@ -53,14 +50,16 @@ else()
add_library(multiplayer SHARED src/multiplayer/multiplayer.cpp)
target_link_libraries(multiplayer mods-headers reborn-patch symbols home feature)
add_library(benchmark SHARED src/benchmark/benchmark.cpp)
target_link_libraries(benchmark mods-headers reborn-patch symbols compat misc media-layer-core)
endif()
if(NOT MCPI_HEADLESS_MODE)
add_library(sound SHARED src/sound/sound.cpp src/sound/repository.cpp)
target_link_libraries(sound mods-headers reborn-patch symbols feature override media-layer-core)
add_library(camera SHARED src/camera/camera.cpp)
target_link_libraries(camera mods-headers reborn-patch symbols feature home)
if(NOT MCPI_HEADLESS_MODE)
target_link_libraries(camera screenshot)
endif()
target_link_libraries(camera mods-headers reborn-patch symbols feature home screenshot)
add_library(input SHARED src/input/input.cpp src/input/bow.c src/input/attack.c src/input/toggle.c src/input/misc.c src/input/drop.cpp src/input/crafting.cpp)
target_link_libraries(input mods-headers reborn-patch symbols creative feature misc media-layer-core)
@ -71,17 +70,27 @@ else()
add_library(touch SHARED src/touch/touch.cpp)
target_link_libraries(touch mods-headers reborn-patch symbols feature)
add_library(textures SHARED src/textures/textures.cpp)
target_link_libraries(textures mods-headers reborn-patch symbols media-layer-core feature misc)
add_library(atlas SHARED src/atlas/atlas.cpp)
target_link_libraries(atlas mods-headers reborn-patch symbols feature media-layer-core)
add_library(title-screen SHARED src/title-screen/title-screen.cpp)
target_link_libraries(title-screen mods-headers reborn-patch symbols feature compat)
add_library(benchmark SHARED src/benchmark/benchmark.cpp)
target_link_libraries(benchmark mods-headers reborn-patch symbols compat misc media-layer-core)
add_library(skin SHARED src/skin/skin.cpp src/skin/loader.cpp)
target_link_libraries(skin mods-headers reborn-patch symbols feature misc textures media-layer-core stb_image)
add_library(screenshot SHARED src/screenshot/screenshot.c)
target_link_libraries(screenshot mods-headers reborn-util media-layer-core stb_image)
endif()
if(NOT MCPI_HEADLESS_MODE)
add_library(textures SHARED src/textures/textures.cpp)
else()
add_library(textures SHARED src/textures/headless.cpp)
endif()
target_link_libraries(textures mods-headers reborn-patch symbols)
if(NOT MCPI_HEADLESS_MODE)
target_link_libraries(textures media-layer-core feature misc stb_image)
endif()
add_library(override SHARED src/override/override.c)
@ -99,11 +108,6 @@ target_link_libraries(options mods-headers reborn-patch symbols feature home)
add_library(bucket SHARED src/bucket/bucket.cpp)
target_link_libraries(bucket mods-headers reborn-patch symbols feature misc)
if(NOT MCPI_HEADLESS_MODE)
add_library(screenshot SHARED src/screenshot/screenshot.c)
target_link_libraries(screenshot mods-headers reborn-util media-layer-core png12)
endif()
add_library(home SHARED src/home/home.c)
target_link_libraries(home mods-headers reborn-patch symbols)
@ -111,22 +115,25 @@ add_library(test SHARED src/test/test.c)
target_link_libraries(test mods-headers reborn-patch home)
add_library(init SHARED src/init/init.c)
target_link_libraries(init mods-headers reborn-util compat game-mode misc death options chat creative bucket home version test media-layer-core)
target_link_libraries(init mods-headers reborn-util compat game-mode misc death options chat creative bucket textures home version test media-layer-core)
if(MCPI_SERVER_MODE)
target_link_libraries(init server)
else()
target_link_libraries(init multiplayer sound camera input sign touch textures atlas title-screen benchmark)
target_link_libraries(init multiplayer benchmark)
endif()
if(NOT MCPI_HEADLESS_MODE)
target_link_libraries(init sound camera input sign touch atlas title-screen skin)
endif()
## Install Mods
set(MODS_TO_INSTALL init compat readdir feature game-mode misc override death options chat creative bucket home version test)
set(MODS_TO_INSTALL init compat readdir feature game-mode misc override death options chat creative bucket textures home version test)
if(MCPI_SERVER_MODE)
list(APPEND MODS_TO_INSTALL server)
else()
list(APPEND MODS_TO_INSTALL multiplayer sound camera input sign touch textures atlas title-screen benchmark)
list(APPEND MODS_TO_INSTALL multiplayer benchmark)
endif()
if(NOT MCPI_HEADLESS_MODE)
list(APPEND MODS_TO_INSTALL screenshot)
list(APPEND MODS_TO_INSTALL sound camera input sign touch atlas title-screen skin screenshot)
endif()
install(TARGETS ${MODS_TO_INSTALL} DESTINATION "${MCPI_INSTALL_DIR}/mods")
# SDK

View File

@ -13,15 +13,19 @@ void init_compat();
void init_server();
#else
void init_multiplayer();
void init_benchmark();
#endif
#ifndef MCPI_HEADLESS_MODE
void init_sound();
void init_input();
void init_sign();
void init_camera();
void init_touch();
void init_textures();
void init_atlas();
void init_title_screen();
void init_skin();
#endif
void init_textures();
void init_creative();
void init_game_mode();
void init_misc();
@ -30,9 +34,6 @@ void init_options();
void init_chat();
void init_bucket();
void init_home();
#ifndef MCPI_SERVER_MODE
void init_benchmark();
#endif
#ifdef __cplusplus
}

View File

@ -0,0 +1,13 @@
#pragma once
#include <GLES/gl.h>
#ifdef __cplusplus
extern "C" {
#endif
void glTexSubImage2D_with_scaling(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLsizei normal_texture_width, GLsizei normal_texture_height, GLenum format, GLenum type, const void *pixels);
#ifdef __cplusplus
}
#endif

View File

@ -5,18 +5,18 @@
#include <cstring>
#include <cstdio>
#include <vector>
#ifndef MCPI_SERVER_MODE
#ifndef MCPI_HEADLESS_MODE
#include <pthread.h>
#endif
#include <symbols/minecraft.h>
#ifndef MCPI_SERVER_MODE
#ifndef MCPI_HEADLESS_MODE
#include <media-layer/core.h>
#endif
#include <mods/init/init.h>
#include <mods/feature/feature.h>
#ifndef MCPI_SERVER_MODE
#ifndef MCPI_HEADLESS_MODE
#include <mods/input/input.h>
#endif
#include "chat-internal.h"
@ -42,7 +42,7 @@ std::string chat_send_api_command(unsigned char *minecraft, char *str) {
}
}
#ifndef MCPI_SERVER_MODE
#ifndef MCPI_HEADLESS_MODE
// Send API Chat Command
static void send_api_chat_command(unsigned char *minecraft, char *str) {
char *command = NULL;
@ -95,7 +95,7 @@ static void ServerSideNetworkHandler_handle_ChatPacket_injection(unsigned char *
}
}
#ifndef MCPI_SERVER_MODE
#ifndef MCPI_HEADLESS_MODE
// Message Queue
static pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
static std::vector<std::string> queue;
@ -144,7 +144,7 @@ void init_chat() {
// Re-Broadcast ChatPacket
patch_address(ServerSideNetworkHandler_handle_ChatPacket_vtable_addr, (void *) ServerSideNetworkHandler_handle_ChatPacket_injection);
// Send Messages On Input Tick
#ifndef MCPI_SERVER_MODE
#ifndef MCPI_HEADLESS_MODE
input_run_on_tick(send_queued_messages);
#endif
}

View File

@ -2,7 +2,7 @@
#include <libreborn/libreborn.h>
// Chat UI Code Is Useless In Headless Mode
#ifndef MCPI_SERVER_MODE
#ifndef MCPI_HEADLESS_MODE
#include <stdio.h>
#include <pthread.h>
@ -34,7 +34,7 @@ static void *chat_thread(__attribute__((unused)) void *nop) {
"--text", "Enter Chat Message:",
NULL
};
char *output = run_command(command, &return_code);
char *output = run_command(command, &return_code, NULL);
// Handle Message
if (output != NULL) {
// Check Return Code

View File

@ -11,6 +11,14 @@
#include <SDL/SDL.h>
#ifndef MCPI_HEADLESS_MODE
#include <media-layer/core.h>
#include <mods/input/input.h>
#include <mods/sign/sign.h>
#include <mods/chat/chat.h>
#include <mods/home/home.h>
#endif
// Custom Title
HOOK(SDL_WM_SetCaption, void, (__attribute__((unused)) const char *title, const char *icon)) {
ensure_SDL_WM_SetCaption();
@ -23,26 +31,19 @@ HOOK(SDL_ShowCursor, int, (int toggle)) {
ensure_SDL_ShowCursor();
return (*real_SDL_ShowCursor)(toggle == SDL_QUERY ? SDL_QUERY : SDL_DISABLE);
}
#endif
#ifndef MCPI_SERVER_MODE
#include <media-layer/core.h>
#include <mods/input/input.h>
#include <mods/sign/sign.h>
#include <mods/chat/chat.h>
#include <mods/home/home.h>
// Intercept SDL Events
HOOK(SDL_PollEvent, int, (SDL_Event *event)) {
// In Server Mode, Exit Requests Are Handled In src/server/server.cpp
// Check If Exit Is Requested
#ifndef MCPI_SERVER_MODE
if (compat_check_exit_requested()) {
// Send SDL_QUIT
SDL_Event new_event;
new_event.type = SDL_QUIT;
SDL_PushEvent(&new_event);
}
#endif
// Poll Events
ensure_SDL_PollEvent();
@ -52,6 +53,7 @@ HOOK(SDL_PollEvent, int, (SDL_Event *event)) {
if (ret == 1 && event != NULL) {
int handled = 0;
#ifndef MCPI_HEADLESS_MODE
switch (event->type) {
case SDL_KEYDOWN: {
// Handle Key Presses
@ -59,9 +61,7 @@ HOOK(SDL_PollEvent, int, (SDL_Event *event)) {
media_toggle_fullscreen();
handled = 1;
} else if (event->key.keysym.sym == SDLK_F2) {
#ifndef MCPI_HEADLESS_MODE
screenshot_take(home_get());
#endif
handled = 1;
} else if (event->key.keysym.sym == SDLK_F1) {
input_hide_gui();
@ -103,11 +103,14 @@ HOOK(SDL_PollEvent, int, (SDL_Event *event)) {
}
case SDL_USEREVENT: {
// SDL_UserEvent Is Never Used In MCPI, So It Is Repurposed For Character Events
sign_key_press((char) event->user.code);
handled = 1;
if (event->user.code == USER_EVENT_CHARACTER) {
sign_key_press((char) event->user.data1);
handled = 1;
}
break;
}
}
#endif
if (handled) {
// Event Was Handled
@ -117,7 +120,6 @@ HOOK(SDL_PollEvent, int, (SDL_Event *event)) {
return ret;
}
#endif
// Exit Handler
static void exit_handler(__attribute__((unused)) int data) {

View File

@ -19,7 +19,7 @@
static char *run_command_proper(const char *command[], bool allow_empty) {
// Run
int return_code;
char *output = run_command(command, &return_code);
char *output = run_command(command, &return_code, NULL);
// Handle Message
if (output != NULL) {

View File

@ -13,15 +13,18 @@ __attribute__((constructor)) static void init() {
init_server();
#else
init_multiplayer();
#endif
#ifndef MCPI_HEADLESS_MODE
init_sound();
init_input();
init_sign();
init_camera();
init_touch();
init_textures();
init_atlas();
init_title_screen();
init_skin();
#endif
init_textures();
init_creative();
init_game_mode();
init_misc();

View File

@ -45,6 +45,23 @@ static int32_t OptionsScreen_handleBackEvent_injection(unsigned char *screen, bo
return 1;
}
// Fix "Sleeping Beauty" Bug
static int32_t InBedScreen_handleBackEvent_injection(unsigned char *screen, bool do_nothing) {
if (!do_nothing) {
// Close Screen
unsigned char *minecraft = *(unsigned char **) (screen + Screen_minecraft_property_offset);
(*Minecraft_setScreen)(minecraft, NULL);
// Stop Sleeping
unsigned char *player = *(unsigned char **) (minecraft + Minecraft_player_property_offset);
if (player != NULL) {
unsigned char *player_vtable = *(unsigned char **) player;
Player_stopSleepInBed_t Player_stopSleepInBed = *(Player_stopSleepInBed_t *) (player_vtable + Player_stopSleepInBed_vtable_offset);
(*Player_stopSleepInBed)(player, 1, 1, 1);
}
}
return 1;
}
// Set Mouse Grab State
static int mouse_grab_state = 0;
void input_set_mouse_grab_state(int state) {
@ -90,6 +107,8 @@ void _init_misc() {
if (enable_misc) {
// Fix OptionsScreen Ignoring The Back Button
patch_address(OptionsScreen_handleBackEvent_vtable_addr, (void *) OptionsScreen_handleBackEvent_injection);
// Fix "Sleeping Beauty" Bug
patch_address(InBedScreen_handleBackEvent_vtable_addr, (void *) InBedScreen_handleBackEvent_injection);
// Disable Opening Inventory Using The Cursor When Cursor Is Hidden
overwrite_calls((void *) Gui_handleClick, (void *) Gui_handleClick_injection);
}

View File

@ -265,6 +265,7 @@ static int32_t FurnaceScreen_handleAddItem_injection(unsigned char *furnace_scre
//
// The default behavior for Touch GUI is to only render the cursor when the mouse is clicking, this fixes that.
// This also makes the cursor always render if the mouse is unlocked, instead of just when there is a Screen showing.
#ifndef MCPI_HEADLESS_MODE
static void GameRenderer_render_injection(unsigned char *game_renderer, float param_1) {
// Call Original Method
(*GameRenderer_render)(game_renderer, param_1);
@ -281,6 +282,7 @@ static void GameRenderer_render_injection(unsigned char *game_renderer, float pa
(*renderCursor)(x, y, minecraft);
}
}
#endif
// Get Real Selected Slot
int32_t misc_get_real_selected_slot(unsigned char *player) {
@ -330,6 +332,167 @@ HOOK(bind, int, (int sockfd, const struct sockaddr *addr, socklen_t addrlen)) {
return (*real_bind)(sockfd, new_addr, addrlen);
}
// Change Grass Color
static int32_t get_color(unsigned char *level_source, int32_t x, int32_t z) {
unsigned char *level_source_vtable = *(unsigned char **) level_source;
LevelSource_getBiome_t LevelSource_getBiome = *(LevelSource_getBiome_t *) (level_source_vtable + LevelSource_getBiome_vtable_offset);
unsigned char *biome = (*LevelSource_getBiome)(level_source, x, z);
if (biome == NULL) {
return 0;
}
return *(int32_t *) (biome + Biome_color_property_offset);
}
#define BIOME_BLEND_SIZE 7
static int32_t GrassTile_getColor_injection(__attribute__((unused)) unsigned char *tile, unsigned char *level_source, int32_t x, __attribute__((unused)) int32_t y, int32_t z) {
int r_sum = 0;
int g_sum = 0;
int b_sum = 0;
int color_sum = 0;
int x_start = x - (BIOME_BLEND_SIZE / 2);
int z_start = z - (BIOME_BLEND_SIZE / 2);
for (int x_offset = 0; x_offset < BIOME_BLEND_SIZE; x_offset++) {
for (int z_offset = 0; z_offset < BIOME_BLEND_SIZE; z_offset++) {
int32_t color = get_color(level_source, x_start + x_offset, z_start + z_offset);
r_sum += (color >> 16) & 0xff;
g_sum += (color >> 8) & 0xff;
b_sum += color & 0xff;
color_sum++;
}
}
int r_avg = r_sum / color_sum;
int g_avg = g_sum / color_sum;
int b_avg = b_sum / color_sum;
return (r_avg << 16) | (g_avg << 8) | b_avg;
}
static int32_t TallGrass_getColor_injection(unsigned char *tile, unsigned char *level_source, int32_t x, int32_t y, int32_t z) {
int32_t original_color = (*TallGrass_getColor)(tile, level_source, x, y, z);
if (original_color == 0x339933) {
return GrassTile_getColor_injection(tile, level_source, x, y, z);
} else {
return original_color;
}
}
// Generate Caves
static void RandomLevelSource_buildSurface_injection(unsigned char *random_level_source, int32_t chunk_x, int32_t chunk_y, unsigned char *chunk_data, unsigned char **biomes) {
// Call Original Method
(*RandomLevelSource_buildSurface)(random_level_source, chunk_x, chunk_y, chunk_data, biomes);
// Get Level
unsigned char *level = *(unsigned char **) (random_level_source + RandomLevelSource_level_property_offset);
// Get Cave Feature
unsigned char *cave_feature = random_level_source + RandomLevelSource_cave_feature_property_offset;
unsigned char *cave_feature_vtable = *(unsigned char **) cave_feature;
// Generate
LargeFeature_apply_t LargeCaveFeature_apply = *(LargeFeature_apply_t *) (cave_feature_vtable + LargeFeature_apply_vtable_offset);
(*LargeCaveFeature_apply)(cave_feature, random_level_source, level, chunk_x, chunk_y, chunk_data, 0);
}
// No Block Tinting
static int32_t Tile_getColor_injection() {
return 0xffffff;
}
// Disable Hostile AI In Creative Mode
static unsigned char *PathfinderMob_findAttackTarget_injection(unsigned char *mob) {
// Call Original Method
unsigned char *mob_vtable = *(unsigned char **) mob;
PathfinderMob_findAttackTarget_t PathfinderMob_findAttackTarget = *(PathfinderMob_findAttackTarget_t *) (mob_vtable + PathfinderMob_findAttackTarget_vtable_offset);
unsigned char *target = (*PathfinderMob_findAttackTarget)(mob);
// Check If Creative Mode
if (target != NULL) {
unsigned char *inventory = *(unsigned char **) (target + Player_inventory_property_offset);
bool is_creative = *(bool *) (inventory + FillingContainer_is_creative_property_offset);
if (is_creative) {
target = NULL;
}
}
// Return
return target;
}
// 3D Chests
static int32_t Tile_getRenderShape_injection(unsigned char *tile) {
if (tile == *Tile_chest) {
// Don't Render "Simple" Chest Model
return -1;
} else {
// Call Original Method
unsigned char *tile_vtable = *(unsigned char **) tile;
Tile_getRenderShape_t Tile_getRenderShape = *(Tile_getRenderShape_t *) (tile_vtable + Tile_getRenderShape_vtable_offset);
return (*Tile_getRenderShape)(tile);
}
}
static unsigned char *ChestTileEntity_injection(unsigned char *tile_entity) {
// Call Original Method
(*ChestTileEntity)(tile_entity);
// Enable Renderer
*(int32_t *) (tile_entity + TileEntity_renderer_id_property_offset) = 1;
// Return
return tile_entity;
}
static bool is_rendering_chest = 0;
static void ModelPart_render_injection(unsigned char *model_part, float scale) {
// Start
is_rendering_chest = 1;
// Call Original Method
(*ModelPart_render)(model_part, scale);
// Stop
is_rendering_chest = 0;
}
static void Tesselator_vertexUV_injection(unsigned char *tesselator, float x, float y, float z, float u, float v) {
// Fix Chest Texture
if (is_rendering_chest) {
v /= 2;
}
// Call Original Method
(*Tesselator_vertexUV)(tesselator, x, y, z, u, v);
}
static bool ChestTileEntity_shouldSave_injection(__attribute__((unused)) unsigned char *tile_entity) {
return 1;
}
// Animated 3D Chest
static unsigned char *ContainerMenu_injection(unsigned char *container_menu, unsigned char *container, int32_t param_1) {
// Call Original Method
(*ContainerMenu)(container_menu, container, param_1);
// Play Animation
unsigned char *tile_entity = container - ChestTileEntity_container_property_offset;
bool is_client = *(bool *) (tile_entity + TileEntity_is_client_property_offset);
if (!is_client) {
unsigned char *container_vtable = *(unsigned char **) container;
Container_startOpen_t Container_startOpen = *(Container_startOpen_t *) (container_vtable + Container_startOpen_vtable_offset);
(*Container_startOpen)(container);
}
// Return
return container_menu;
}
static unsigned char *ContainerMenu_destructor_injection(unsigned char *container_menu) {
// Play Animation
unsigned char *container = *(unsigned char **) (container_menu + ContainerMenu_container_property_offset);
unsigned char *tile_entity = container - ChestTileEntity_container_property_offset;
bool is_client = *(bool *) (tile_entity + TileEntity_is_client_property_offset);
if (!is_client) {
unsigned char *container_vtable = *(unsigned char **) container;
Container_stopOpen_t Container_stopOpen = *(Container_stopOpen_t *) (container_vtable + Container_stopOpen_vtable_offset);
(*Container_stopOpen)(container);
}
// Call Original Method
return (*ContainerMenu_destructor)(container_menu);
}
// Init
static void nop() {
}
@ -388,6 +551,10 @@ void init_misc() {
overwrite_calls((void *) FurnaceScreen_handleAddItem, (void *) FurnaceScreen_handleAddItem_injection);
}
#ifdef MCPI_HEADLESS_MODE
// Don't Render Game In Headless Mode
overwrite_calls((void *) GameRenderer_render, (void *) nop);
#else
// Improved Cursor Rendering
if (feature_has("Improved Cursor Rendering", server_disabled)) {
// Disable Normal Cursor Rendering
@ -396,6 +563,7 @@ void init_misc() {
// Add Custom Cursor Rendering
overwrite_calls((void *) GameRenderer_render, (void *) GameRenderer_render_injection);
}
#endif
// Disable V-Sync
if (feature_has("Disable V-Sync", server_enabled)) {
@ -430,6 +598,63 @@ void init_misc() {
patch((void *) 0x19fa0, nop_patch);
}
// Change Grass Color
if (feature_has("Add Biome Colors To Grass", server_disabled)) {
patch_address((void *) GrassTile_getColor_vtable_addr, (void *) GrassTile_getColor_injection);
patch_address((void *) TallGrass_getColor_vtable_addr, (void *) TallGrass_getColor_injection);
}
// Generate Caves
if (feature_has("Generate Caves", server_auto)) {
overwrite_calls((void *) RandomLevelSource_buildSurface, (void *) RandomLevelSource_buildSurface_injection);
}
// Disable Block Tinting
if (feature_has("Disable Block Tinting", server_disabled)) {
patch_address((void *) GrassTile_getColor_vtable_addr, (void *) Tile_getColor_injection);
patch_address((void *) TallGrass_getColor_vtable_addr, (void *) Tile_getColor_injection);
patch_address((void *) StemTile_getColor_vtable_addr, (void *) Tile_getColor_injection);
patch_address((void *) LeafTile_getColor_vtable_addr, (void *) Tile_getColor_injection);
overwrite((void *) LiquidTile_getColor, (void *) Tile_getColor_injection);
}
// Custom GUI Scale
const char *gui_scale_str = getenv("MCPI_GUI_SCALE");
if (gui_scale_str != NULL) {
unsigned char nop_patch[4] = {0x00, 0xf0, 0x20, 0xe3}; // "nop"
patch((void *) 0x173e8, nop_patch);
patch((void *) 0x173f0, nop_patch);
float gui_scale = strtof(gui_scale_str, NULL);
uint32_t gui_scale_raw;
memcpy(&gui_scale_raw, &gui_scale, sizeof (gui_scale_raw));
patch_address((void *) 0x17520, (void *) gui_scale_raw);
}
// Disable Hostile AI In Creative Mode
if (feature_has("Disable Hostile AI In Creative Mode", server_enabled)) {
overwrite_call((void *) 0x83b8c, (void *) PathfinderMob_findAttackTarget_injection);
}
// 3D Chests
if (feature_has("3D Chest Model", server_disabled)) {
overwrite_call((void *) 0x5e830, (void *) Tile_getRenderShape_injection);
overwrite_calls((void *) ChestTileEntity, (void *) ChestTileEntity_injection);
overwrite_call((void *) 0x6655c, (void *) ModelPart_render_injection);
overwrite_call((void *) 0x66568, (void *) ModelPart_render_injection);
overwrite_call((void *) 0x66574, (void *) ModelPart_render_injection);
overwrite_calls((void *) Tesselator_vertexUV, (void *) Tesselator_vertexUV_injection);
unsigned char chest_model_patch[4] = {0x13, 0x20, 0xa0, 0xe3}; // "mov r2, #0x13"
patch((void *) 0x66fc8, chest_model_patch);
unsigned char chest_color_patch[4] = {0x00, 0xf0, 0x20, 0xe3}; // "nop"
patch((void *) 0x66404, chest_color_patch);
// Animation
overwrite_calls((void *) ContainerMenu, (void *) ContainerMenu_injection);
overwrite_calls((void *) ContainerMenu_destructor, (void *) ContainerMenu_destructor_injection);
patch_address(ContainerMenu_destructor_vtable_addr, (void *) ContainerMenu_destructor_injection);
}
patch_address((void *) 0x115b48, (void *) ChestTileEntity_shouldSave_injection);
// Init C++ And Logging
_init_misc_cpp();
_init_misc_logging();

View File

@ -13,15 +13,25 @@
// Read Asset File
static AppPlatform_readAssetFile_return_value AppPlatform_readAssetFile_injection(__attribute__((unused)) unsigned char *app_platform, std::string const& path) {
// Open File
std::ifstream stream("data/" + path, std::ios_base::binary | std::ios_base::ate);
if (!stream) {
// Does Not Exist
AppPlatform_readAssetFile_return_value ret;
ret.length = -1;
ret.data = NULL;
return ret;
}
// Read File
std::string full_path("data/");
full_path.append(path);
std::ifstream stream(full_path);
std::string str((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());
long len = stream.tellg();
char *buf = new char[len];
stream.seekg(0, stream.beg);
stream.read(buf, len);
stream.close();
// Return String
AppPlatform_readAssetFile_return_value ret;
ret.length = str.length();
ret.data = strdup(str.c_str());
ret.length = len;
ret.data = strdup(buf);
return ret;
}

View File

@ -38,7 +38,7 @@ static void load_servers() {
// Write Defaults
std::ofstream server_list_file_output(file);
server_list_file_output << "# External Servers File\n";
server_list_file_output << "thebrokenrail.com\n";
server_list_file_output << "# Example: thebrokenrail.com\n";
server_list_file_output.close();
// Re-Open Stream
server_list_file = std::ifstream(file);

View File

@ -27,6 +27,12 @@ HOOK(access, int, (const char *pathname, int mode)) {
// Get Override Path For File (If It Exists)
char *override_get_path(const char *filename) {
// Custom Skin
if (starts_with(filename, "data/images/$")) {
// Fallback Texture
filename = "data/images/mob/char.png";
}
// Get MCPI Home Path
char *home_path = home_get();
// Get Asset Override Path

View File

@ -6,7 +6,8 @@
#include <errno.h>
#include <sys/stat.h>
#include <png.h>
#include "stb_image.h"
#include "stb_image_write.h"
#include <libreborn/libreborn.h>
#include <GLES/gl.h>
@ -31,68 +32,11 @@ static void ensure_screenshots_folder(char *screenshots) {
// Take Screenshot
static int save_png(const char *filename, unsigned char *pixels, int line_size, int width, int height) {
// Return value
int ret = 0;
// Setup
stbi_flip_vertically_on_write(1);
// Variables
png_structp png = NULL;
png_infop info = NULL;
FILE *file = NULL;
png_colorp palette = NULL;
png_bytep rows[height];
for (int i = 0; i < height; ++i) {
rows[height - i - 1] = (png_bytep) (&pixels[i * line_size]);
}
// Init
png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png) {
ret = 1;
goto ret;
}
info = png_create_info_struct(png);
if (!info) {
ret = 1;
goto ret;
}
// Open File
file = fopen(filename, "wb");
if (!file) {
ret = 1;
goto ret;
}
// Prepare To Write
png_init_io(png, file);
png_set_IHDR(png, info, width, height, 8 /* Depth */, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
palette = (png_colorp) png_malloc(png, PNG_MAX_PALETTE_LENGTH * sizeof(png_color));
if (!palette) {
ret = 1;
goto ret;
}
png_set_PLTE(png, info, palette, PNG_MAX_PALETTE_LENGTH);
png_write_info(png, info);
png_set_packing(png);
// Write
png_write_image(png, rows);
png_write_end(png, info);
ret:
// Free
if (palette != NULL) {
png_free(png, palette);
}
if (file != NULL) {
fclose(file);
}
if (png != NULL) {
png_destroy_write_struct(&png, &info);
}
// Return
return ret;
// Write Image
return !stbi_write_png(filename, width, height, 4, pixels, line_size);
}
void screenshot_take(char *home) {
// Get Directory
@ -144,7 +88,8 @@ void screenshot_take(char *home) {
int size = height * line_size;
// Read Pixels
unsigned char pixels[size];
unsigned char *pixels = (unsigned char *) malloc(size);
ALLOC_CHECK(pixels);
glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
// Save Image
@ -157,4 +102,5 @@ void screenshot_take(char *home) {
// Free
free(file);
free(screenshots);
free(pixels);
}

View File

@ -60,7 +60,8 @@ static ServerProperties &get_server_properties() {
#define DEFAULT_WORLD_NAME "world"
#define DEFAULT_MAX_PLAYERS "4"
#define DEFAULT_WHITELIST "false"
#define DEFAULT_DEATH_MESSAGES "false"
#define DEFAULT_DEATH_MESSAGES "true"
#define DEFAULT_GENERATE_CAVES "true"
// Get World Name
static std::string get_world_name() {
@ -487,6 +488,9 @@ static const char *get_features() {
if (get_server_properties().get_bool("death-messages", DEFAULT_DEATH_MESSAGES)) {
features += "Implement Death Messages|";
}
if (get_server_properties().get_bool("generate-caves", DEFAULT_GENERATE_CAVES)) {
features += "Generate Caves|";
}
}
return features.c_str();
}
@ -536,6 +540,8 @@ static void server_init() {
properties_file_output << "whitelist=" DEFAULT_WHITELIST "\n";
properties_file_output << "# Enable Death Messages\n";
properties_file_output << "death-messages=" DEFAULT_DEATH_MESSAGES "\n";
properties_file_output << "# Generate Caves\n";
properties_file_output << "generate-caves=" DEFAULT_GENERATE_CAVES "\n";
properties_file_output.close();
// Re-Open File
properties_file = std::ifstream(file);

2
mods/src/skin/README.md Normal file
View File

@ -0,0 +1,2 @@
# ``skin`` Mod
This mod adds custom skin loading.

144
mods/src/skin/loader.cpp Normal file
View File

@ -0,0 +1,144 @@
#include <pthread.h>
#include <vector>
#include <libreborn/libreborn.h>
#include <symbols/minecraft.h>
#include <GLES/gl.h>
#include <mods/misc/misc.h>
#include <mods/textures/textures.h>
#include "skin-internal.h"
#include "stb_image.h"
// Constants
#define SKIN_WIDTH 64
#define SKIN_HEIGHT 32
// Loading Pending Skins
struct pending_skin {
int32_t texture_id;
char *data;
int size;
};
static std::vector<pending_skin> &get_pending_skins() {
static std::vector<pending_skin> pending_skins;
return pending_skins;
}
static pthread_mutex_t pending_skins_lock = PTHREAD_MUTEX_INITIALIZER;
static void load_pending_skins(__attribute__((unused)) unsigned char *minecraft) {
// Lock
pthread_mutex_lock(&pending_skins_lock);
// Loop
for (pending_skin &skin : get_pending_skins()) {
// Read PNG Info
int width = 0, height = 0, channels = 0;
stbi_uc *img = stbi_load_from_memory((unsigned char *) skin.data, skin.size, &width, &height, &channels, STBI_rgb_alpha);
if (width != SKIN_WIDTH || height != SKIN_HEIGHT) {
continue;
}
// Load Texture
GLint last_texture;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
glBindTexture(GL_TEXTURE_2D, skin.texture_id);
glTexSubImage2D_with_scaling(GL_TEXTURE_2D, 0, 0, 0, width, height, SKIN_WIDTH, SKIN_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, img);
glBindTexture(GL_TEXTURE_2D, last_texture);
// Free
stbi_image_free(img);
}
// Free
for (pending_skin &skin : get_pending_skins()) {
free(skin.data);
}
// Clear
get_pending_skins().clear();
// Unlock
pthread_mutex_unlock(&pending_skins_lock);
}
// Skin Server
static std::string get_skin_server() {
const char *custom_server = getenv("MCPI_SKIN_SERVER");
if (custom_server != NULL) {
return custom_server;
} else {
return MCPI_SKIN_SERVER;
}
}
// Skin Loader
struct loader_data {
int32_t texture_id;
std::string name;
};
static void *loader_thread(void *user_data) {
// Loader Data
loader_data *data = (loader_data *) user_data;
// Download
std::string url = get_skin_server() + '/' + data->name + ".png";
int return_code;
const char *command[] = {"wget", "-O", "-", url.c_str(), NULL};
size_t output_size = 0;
char *output = run_command(command, &return_code, &output_size);
// Check Success
if (output != NULL && is_exit_status_success(return_code)) {
// Success
DEBUG("Downloaded Skin: %s", data->name.c_str());
// Add To Pending Skins
pending_skin skin;
skin.texture_id = data->texture_id;
skin.data = output;
skin.size = (int) output_size;
pthread_mutex_lock(&pending_skins_lock);
get_pending_skins().push_back(skin);
pthread_mutex_unlock(&pending_skins_lock);
} else {
// Failure
WARN("Failed To Download Skin: %s", data->name.c_str());
free(output);
}
// Free
delete data;
return NULL;
}
// Intercept Texture Creation
static int32_t Textures_assignTexture_injection(unsigned char *textures, std::string const& name, unsigned char *data) {
// Call Original Method
int32_t id = (*Textures_assignTexture)(textures, name, data);
// Load Skin
if (starts_with(name.c_str(), "$")) {
loader_data *user_data = new loader_data;
user_data->name = name.substr(1);
DEBUG("Loading Skin: %s", user_data->name.c_str());
user_data->texture_id = id;
// Start Thread
pthread_t thread;
pthread_create(&thread, NULL, loader_thread, (void *) user_data);
}
// Return
return id;
}
// Init
void _init_skin_loader() {
// Intercept Texture Creation
overwrite_calls((void *) Textures_assignTexture, (void *) Textures_assignTexture_injection);
// Pending Skins
misc_run_on_tick(load_pending_skins);
// Log
DEBUG("Skin Server: %s", get_skin_server().c_str());
}

View File

@ -0,0 +1,3 @@
#pragma once
__attribute__((visibility("internal"))) void _init_skin_loader();

97
mods/src/skin/skin.cpp Normal file
View File

@ -0,0 +1,97 @@
#include <libreborn/libreborn.h>
#include <symbols/minecraft.h>
#include <mods/init/init.h>
#include <mods/feature/feature.h>
#include "skin-internal.h"
// Base64 Encode (https://gist.github.com/tomykaira/f0fd86b6c73063283afe550bc5d77594)
static std::string base64_encode(const std::string data) {
static constexpr char encoding_table[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
};
size_t in_len = data.size();
size_t out_len = 4 * ((in_len + 2) / 3);
std::string ret(out_len, '\0');
size_t i;
char *p = const_cast<char*>(ret.c_str());
for (i = 0; i < in_len - 2; i += 3) {
*p++ = encoding_table[(data[i] >> 2) & 0x3f];
*p++ = encoding_table[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xf0) >> 4)];
*p++ = encoding_table[((data[i + 1] & 0xf) << 2) | ((int) (data[i + 2] & 0xc0) >> 6)];
*p++ = encoding_table[data[i + 2] & 0x3f];
}
if (i < in_len) {
*p++ = encoding_table[(data[i] >> 2) & 0x3f];
if (i == (in_len - 1)) {
*p++ = encoding_table[((data[i] & 0x3) << 4)];
*p++ = '=';
}
else {
*p++ = encoding_table[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xf0) >> 4)];
*p++ = encoding_table[((data[i + 1] & 0xf) << 2)];
}
*p++ = '=';
}
return ret;
}
// Change Texture For Player Entities
static void Player_username_assign_injection(std::string *target, std::string *username) {
// Call Original Method
*target = *username;
// Get Player
unsigned char *player = ((unsigned char *) target) - Player_username_property_offset;
// Get Texture
std::string *texture = (std::string *) (player + Mob_texture_property_offset);
// Set Texture
*texture = '$' + base64_encode(*username);
}
static void Player_username_assign_injection_2(std::string *target, const char *username) {
std::string username_str = username;
Player_username_assign_injection(target, &username_str);
}
// Change Texture For HUD
static int32_t Textures_loadAndBindTexture_injection(unsigned char *textures, __attribute__((unused)) std::string const& name) {
// Change Texture
static std::string new_texture;
if (new_texture.length() == 0) {
std::string username = base64_encode(*default_username);
new_texture = '$' + username;
}
// Call Original Method
return (*Textures_loadAndBindTexture)(textures, new_texture);
}
// Init
void init_skin() {
// Check Feature Flag
if (feature_has("Load Custom Skins", server_disabled)) {
// LocalPlayer
overwrite_call((void *) 0x44c28, (void *) Player_username_assign_injection);
// RemotePlayer
overwrite_call((void *) 0x6ce58, (void *) Player_username_assign_injection_2);
// ServerPlayer
overwrite_call((void *) 0x7639c, (void *) Player_username_assign_injection_2);
// HUD
overwrite_call((void *) 0x4c6d0, (void *) Textures_loadAndBindTexture_injection);
// Loader
_init_skin_loader();
}
}

View File

@ -59,10 +59,8 @@ std::string _sound_get_source_file() {
static void play(std::string name, float x, float y, float z, float volume, float pitch, bool is_ui) {
std::string source = _sound_get_source_file();
std::string resolved_name = _sound_pick(name);
if (pitch < 0.5f) {
pitch = 0.5f;
} else if (pitch > 2.0f) {
pitch = 2.0f;
if (pitch < 0.01f) {
pitch = 1;
}
if (source.size() > 0 && resolved_name.size() > 0) {
media_audio_play(source.c_str(), resolved_name.c_str(), x, y, z, pitch, volume, is_ui);

View File

@ -0,0 +1,14 @@
#include <libreborn/libreborn.h>
#include <symbols/minecraft.h>
#include <mods/init/init.h>
// Disable Texture Loading
static Texture AppPlatform_linux_loadTexture_injection(__attribute__((unused)) unsigned char *app_platform, __attribute__((unused)) std::string const& path, __attribute__((unused)) bool b) {
return Texture();
}
// Init
void init_textures() {
// Disable Texture Loading
overwrite((void *) AppPlatform_linux_loadTexture, (void *) AppPlatform_linux_loadTexture_injection);
}

View File

@ -9,8 +9,11 @@
#include <mods/misc/misc.h>
#include <mods/feature/feature.h>
#include <mods/textures/textures.h>
#include <mods/init/init.h>
#include "stb_image.h"
// Animated Water
static void Minecraft_tick_injection(unsigned char *minecraft) {
// Tick Dynamic Textures
@ -124,7 +127,7 @@ static void *scale_texture(const unsigned char *src, GLsizei old_width, GLsizei
}
// Scale Animated Textures
static void Textures_tick_glTexSubImage2D_injection(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels) {
void glTexSubImage2D_with_scaling(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLsizei normal_texture_width, GLsizei normal_texture_height, GLenum format, GLenum type, const void *pixels) {
// Get Current Texture Size
GLint current_texture;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &current_texture);
@ -133,8 +136,8 @@ static void Textures_tick_glTexSubImage2D_injection(GLenum target, GLint level,
get_texture_size(current_texture, &texture_width, &texture_height);
// Calculate Factor
float width_factor = ((float) texture_width) / 256.0f;
float height_factor = ((float) texture_height) / 256.0f;
float width_factor = ((float) texture_width) / ((float) normal_texture_width);
float height_factor = ((float) texture_height) / ((float) normal_texture_height);
// Only Scale If Needed
if (width_factor == 1.0f && height_factor == 1.0f) {
@ -161,6 +164,41 @@ static void Textures_tick_glTexSubImage2D_injection(GLenum target, GLint level,
free(new_pixels);
}
}
static void Textures_tick_glTexSubImage2D_injection(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels) {
glTexSubImage2D_with_scaling(target, level, xoffset, yoffset, width, height, 256, 256, format, type, pixels);
}
// Load Textures
static Texture AppPlatform_linux_loadTexture_injection(__attribute__((unused)) unsigned char *app_platform, std::string const& path, bool b) {
Texture out;
std::string real_path = path;
if (b) {
real_path = "data/images/" + real_path;
}
// Read Image
int width = 0, height = 0, channels = 0;
stbi_uc *img = stbi_load(real_path.c_str(), &width, &height, &channels, STBI_rgb_alpha);
if (!img)
{
// Failed To Parse Image
WARN("Unable To Load Texture: %s", real_path.c_str());
return out;
}
// Copy Image
unsigned char *img2 = new unsigned char[width * height * channels];
memcpy(img2, img, width * height * channels);
stbi_image_free(img);
// Create Texture
out.width = width;
out.height = height;
out.data = img2;
// Return
return out;
}
// Init
void init_textures() {
@ -171,4 +209,7 @@ void init_textures() {
// Scale Animated Textures
overwrite_call((void *) 0x53274, (void *) Textures_tick_glTexSubImage2D_injection);
// Load Textures
overwrite((void *) AppPlatform_linux_loadTexture, (void *) AppPlatform_linux_loadTexture_injection);
}

Some files were not shown because too many files have changed in this diff Show More