Skocz do zawartości

[basE91] - konwersja binarno-tekstowa.


Rekomendowane odpowiedzi

Być może są tu tacy, którzy mnie pamiętają. Ja w każdym razie kojarzę parę ksywek.Ostatnio ponownie zainteresowałem się GM. I kojarzę tę trzecią odsłonę GMClan-owego forum. I kojarzę, że pamiętam jeszcze hasło. Nie lubię pożegnań ani powitań, więc z tym zapraszam ew. na PW. A wszystkich innych witam serdecznie po raz pierwszy.

 

Koniec offtopa. Będzie rozlegle.

 

<funkcja base91_init>

GML
/*

basE91.

 

Czyli system liczbowy oparty na podstawie 91.

Tak się jakoś składa, że 91 znaków ASCII

(litery, liczby i znaki specjalne - z pominięciem

znaków ' \ - oraz znaków tzw. niedrukowalnych)

mogą być świetnie użyte do reprezentacji danych

binarnych.

 

Dzięki temu można przesyłać tekst, muzykę, obrazy,

pliki wykonywalne etc. bez obawy o utratę danych

w przypadku, gdy obie strony używają różnych kodowań.

 

W praktyce - oznacza to także możliwość wygenerowania

binarnego pliku na serwerze dynamicznie, "karmiąc"

skrypt zakodowanymi danymi poprzez HTTP - w tym także

w Game Makerze. Innymi słowy - wysyłanie i odbieranie

plików jedynie poprzez odpowiednie spreparowanie

odnośnika.

 

basE91 ma najwyższą efektywność - ok. 82% [źr. Wikipedia].

Oznacza to, że na 100 wysłanych bajtów jedynie 18 z nich

stanowi nadwyżkę - będącą efektem zmiejszenia liczby

dostępnych znaków do wyświetlenia.

 

I tak...

 

http://example.com/upload/dJR2LwoZ?Xx=nuH*!Bf!rvpa$iDu_8l*@E=C1]OCy$Fn:Eso:zYh>w^C}!n3B

 

Sprawiłoby, by serwer example.com odebrał plik UTF-8

ze swojsko brzmiącą zawartością:

 

Pchnąć w tę łódź jeża lub ośm skrzyń fig

 

Dopóki przesyłany jest tekst w standardzie rozszerzo-

nego ASCII (jeden znak zajmuje jeden bajt) - co

wyklucza wszystkie języki poza angielskim, który

składa się ze "standardowych" liter łacińskiego

alfabetu, można bez obawy korzystać z konkurencyjnych

metod konwersji danych binarnych na tekst, jak

znany wielu base64 czy uuencode.

 

Problemem jest jednak złe radzenie sobie z UTF-8

(standard kodowania wykorzystywany także przez

GM Studio - i dziękujmy Morganowi Freemanowi, że

tak się stało! Unicode bardzo wiele ułatwia)

oraz nadmiar danych.

 

Jeżeli nadal nie widzisz do czego przydaje się

konwersja binarno-tekstowa, spróbuj kiedyś

wysłać z GM zrzut ekranu z gry na serwer

przy użyciu eventu z rodziny Asynchronous.

 

Co do skryptu: nie jestem autorem samego algorytmu.

Ba, nawet nie całkiem go rozumiem. Wydał mi się

jednak na tyle interesujący i użyteczny, iż

postanowiłem zaimplementować go w GM Studio.

 

Autorem oryginalnego algorytmu jest Joachim Henke.

Udostępnia go on na licencji BSD, pod adresem

 

http://base91.sourceforge.net/

 

Autorem tej implementacji / tłumaczenia jest Phariseus.

Nie włożył on w nią zbyt dużo wysiłku.

Jest pewny, że wiele rzeczy możnaby zrobić lepiej,

szybciej, tu zoptymizować, tam kokardkę nakleić.

Jest też pewny, że znajdą się ludzie, którym się

zechce to zrobić. W związku z tym i powyższym,

ta implementacja również udostępniona jest

na licencji BSD. Wykonana została dla portalu

GMClan.org - Game Maker Polska, do którego

autor implementacji żywi ponad 10-letni sentyment

- mimo bardzo, bardzo długiego okresu bezczynności.

 

basE91 nie jest standardem ISO, lecz jest

implementowany na wielu platformach.

Stanowi także wolne oprogramowanie.

 

A za każdym razem, gdy nabywasz, modyfikujesz

i udostępniasz wolne oprogramowanie,

Richardowi Stallmanowi rośnie jeden włos na brodzie.

 

GMowa implementacja jest dosyć prosta,

na chwilę obecną przyjmuje na wejściu ciągi znaków.

(wkrótce: pliki).

 

 

Do adremu, jak to mawiają.

- Nicodemus "Phariseus" J. Bernards, 2015

 

PS: Skrypt jest wybitnie nie-idiotoodporny.

Karmisz nieoczekiwanymi / pustymi / nieprawidłowymi

wartościami na własną odpowiedzialność.

 

*/

 

 

/*

Zacznijmy od zainicjowania funkcyi. Ta funkcja

powinna być wywołana przed pierwszym zakodowaniem

lub zdekodowaniem.

 

Definiujemy dwie globalne tablice.

 

 

Pierwsza, b91_enc, zawiera znaki systemu

dziewięćdziesięciojedynkowego (zgadzam się,

idiotyczna nazwa), które posłużą za "cyfry".

Jeżeli intryguje cię to zagadnienie, polecam

http://pl.wikipedia.org/wiki/System_liczbowy

 

Druga tablica tłumaczy z owego egzotycznego

systemu na ludzki, czyli dziesiętny.

 

Nic nie stoi na przeszkodzie, by wykorzystać

ds_list. Ja byłem na to zbyt leniwy.

*/

 

global.b91_enc[0]='A';

global.b91_enc[1]='B';

global.b91_enc[2]='C';

global.b91_enc[3]='D';

global.b91_enc[4]='E';

global.b91_enc[5]='F';

global.b91_enc[6]='G';

global.b91_enc[7]='H';

global.b91_enc[8]='I';

global.b91_enc[9]='J';

global.b91_enc[10]='K';

global.b91_enc[11]='L';

global.b91_enc[12]='M';

global.b91_enc[13]='N';

global.b91_enc[14]='O';

global.b91_enc[15]='P';

global.b91_enc[16]='Q';

global.b91_enc[17]='R';

global.b91_enc[18]='S';

global.b91_enc[19]='T';

global.b91_enc[20]='U';

global.b91_enc[21]='V';

global.b91_enc[22]='W';

global.b91_enc[23]='X';

global.b91_enc[24]='Y';

global.b91_enc[25]='Z';

global.b91_enc[26]='a';

global.b91_enc[27]='b';

global.b91_enc[28]='c';

global.b91_enc[29]='d';

global.b91_enc[30]='e';

global.b91_enc[31]='f';

global.b91_enc[32]='g';

global.b91_enc[33]='h';

global.b91_enc[34]='i';

global.b91_enc[35]='j';

global.b91_enc[36]='k';

global.b91_enc[37]='l';

global.b91_enc[38]='m';

global.b91_enc[39]='n';

global.b91_enc[40]='o';

global.b91_enc[41]='p';

global.b91_enc[42]='q';

global.b91_enc[43]='r';

global.b91_enc[44]='s';

global.b91_enc[45]='t';

global.b91_enc[46]='u';

global.b91_enc[47]='v';

global.b91_enc[48]='w';

global.b91_enc[49]='x';

global.b91_enc[50]='y';

global.b91_enc[51]='z';

global.b91_enc[52]='0';

global.b91_enc[53]='1';

global.b91_enc[54]='2';

global.b91_enc[55]='3';

global.b91_enc[56]='4';

global.b91_enc[57]='5';

global.b91_enc[58]='6';

global.b91_enc[59]='7';

global.b91_enc[60]='8';

global.b91_enc[61]='9';

global.b91_enc[62]='!';

global.b91_enc[63]='#';

global.b91_enc[64]='$';

global.b91_enc[65]='%';

global.b91_enc[66]='&';

global.b91_enc[67]='(';

global.b91_enc[68]=')';

global.b91_enc[69]='*';

global.b91_enc[70]='+';

global.b91_enc[71]=',';

global.b91_enc[72]='.';

global.b91_enc[73]='/';

global.b91_enc[74]=':';

global.b91_enc[75]=';';

global.b91_enc[76]='<';

global.b91_enc[77]='=';

global.b91_enc[78]='>';

global.b91_enc[79]='?';

global.b91_enc[80]='@';

global.b91_enc[81]='[';

global.b91_enc[82]=']';

global.b91_enc[83]='^';

global.b91_enc[84]='_';

global.b91_enc[85]='`';

global.b91_enc[86]='{';

global.b91_enc[87]='|';

global.b91_enc[88]='}';

global.b91_enc[89]='~';

global.b91_enc[90]='"';

 

global.b91_dec[ord('A')]=0;

global.b91_dec[ord('B')]=1;

global.b91_dec[ord('C')]=2;

global.b91_dec[ord('D')]=3;

global.b91_dec[ord('E')]=4;

global.b91_dec[ord('F')]=5;

global.b91_dec[ord('G')]=6;

global.b91_dec[ord('H')]=7;

global.b91_dec[ord('I')]=8;

global.b91_dec[ord('J')]=9;

global.b91_dec[ord('K')]=10;

global.b91_dec[ord('L')]=11;

global.b91_dec[ord('M')]=12;

global.b91_dec[ord('N')]=13;

global.b91_dec[ord('O')]=14;

global.b91_dec[ord('P')]=15;

global.b91_dec[ord('Q')]=16;

global.b91_dec[ord('R')]=17;

global.b91_dec[ord('S')]=18;

global.b91_dec[ord('T')]=19;

global.b91_dec[ord('U')]=20;

global.b91_dec[ord('V')]=21;

global.b91_dec[ord('W')]=22;

global.b91_dec[ord('X')]=23;

global.b91_dec[ord('Y')]=24;

global.b91_dec[ord('Z')]=25;

global.b91_dec[ord('a')]=26;

global.b91_dec[ord('b')]=27;

global.b91_dec[ord('c')]=28;

global.b91_dec[ord('d')]=29;

global.b91_dec[ord('e')]=30;

global.b91_dec[ord('f')]=31;

global.b91_dec[ord('g')]=32;

global.b91_dec[ord('h')]=33;

global.b91_dec[ord('i')]=34;

global.b91_dec[ord('j')]=35;

global.b91_dec[ord('k')]=36;

global.b91_dec[ord('l')]=37;

global.b91_dec[ord('m')]=38;

global.b91_dec[ord('n')]=39;

global.b91_dec[ord('o')]=40;

global.b91_dec[ord('p')]=41;

global.b91_dec[ord('q')]=42;

global.b91_dec[ord('r')]=43;

global.b91_dec[ord('s')]=44;

global.b91_dec[ord('t')]=45;

global.b91_dec[ord('u')]=46;

global.b91_dec[ord('v')]=47;

global.b91_dec[ord('w')]=48;

global.b91_dec[ord('x')]=49;

global.b91_dec[ord('y')]=50;

global.b91_dec[ord('z')]=51;

global.b91_dec[ord('0')]=52;

global.b91_dec[ord('1')]=53;

global.b91_dec[ord('2')]=54;

global.b91_dec[ord('3')]=55;

global.b91_dec[ord('4')]=56;

global.b91_dec[ord('5')]=57;

global.b91_dec[ord('6')]=58;

global.b91_dec[ord('7')]=59;

global.b91_dec[ord('8')]=60;

global.b91_dec[ord('9')]=61;

global.b91_dec[ord('!')]=62;

global.b91_dec[ord('#')]=63;

global.b91_dec[ord('$')]=64;

global.b91_dec[ord('%')]=65;

global.b91_dec[ord('&')]=66;

global.b91_dec[ord('(')]=67;

global.b91_dec[ord(')')]=68;

global.b91_dec[ord('*')]=69;

global.b91_dec[ord('+')]=70;

global.b91_dec[ord(',')]=71;

global.b91_dec[ord('.')]=72;

global.b91_dec[ord('/')]=73;

global.b91_dec[ord(':')]=74;

global.b91_dec[ord(';')]=75;

global.b91_dec[ord('<')]=76;

global.b91_dec[ord('=')]=77;

global.b91_dec[ord('>')]=78;

global.b91_dec[ord('?')]=79;

global.b91_dec[ord('@')]=80;

global.b91_dec[ord('[')]=81;

global.b91_dec[ord(']')]=82;

global.b91_dec[ord('^')]=83;

global.b91_dec[ord('_')]=84;

global.b91_dec[ord('`')]=85;

global.b91_dec[ord('{')]=86;

global.b91_dec[ord('|')]=87;

global.b91_dec[ord('}')]=88;

global.b91_dec[ord('~')]=89;

global.b91_dec[ord('"')]=90;

 

<función base91_encode>

GML
//Funkcja przyjmuje jeden argument - ciąg znaków.

//Zwraca ona także jeden argument. I także ciąg znaków.

d=argument[0]; //łańcuch znaków do zakodowania

l = string_length(d); //liczba znaków łańcucha

b=0; //tajemnicza zmienna numer 1

n=0; //tajemnicza zmienna number b (co, że niby powinno być 2?)

o=''; //łańcuch znaków do zwrócenia

 

/*

Teraz stworzymy plik, na którym będziemy pracować.

Tak, funkcja tworzy tymczasowy plik. Bardzo tymczasowy.

Tak tymczasowy, że usuwany jest on jeszcze podczas wykonywania funkcji.

 

Za chwilę dowiemy się, dlaczego. Oto co musimy zrobić:

 

Otwieramy plik w trybie tekstowym do zapisu.

Wpisujemy wartość łańcucha wejściowego do pliku.

Zamykamy plik.

 

Otwieramy plik w trybie binarnym do odczytu.

Zapamiętujemy rozmiar pliku w bajtach.

Wartość każdego bajta zapamiętujemy w tablicy bajtów.

I dopiero wtedy przechodzimy do głównej pętli funkcji.

Jej pierwsza linia odczytuje i-ty bajt z tablicy bajtów,

przesunięty bitowo w lewo o wartość n.

 

Dobra, chwila. Ale właściwie po co tyle zachodu? Czy nie łatwiejsze

byłoby użycie gołej funkcji ord, zwracającej kod ascii danego znaku,

zamiast wielokrotne operowanie na plikach?

 

Problemem są polskie litery. I nie tylko polskie. I nie tylko litery.

Właściwie wszystko, co przekracza zakres 8 bitów, czyli rozszerzone ASCII.

Jako że ta implementacja funkcji operuje na pojedynczych bajtach

- a na przykład polskie litery kodowane w utf8 składają się z dwóch bajtów -

więcej patyczkowania byłoby z "rozstrzeliwaniem" znaków na bajty poprzez

funkcję. O wiele łatwiej jest najpierw zapisać łańcuch wejściowy tekstowo,

a odczytać bajt po bajcie - wtedy dzieje się to automatycznie.

 

Poniżej moja implementacja basE91. Jest to niemalże dosłowne tłumaczenie

z PHP4, z kilkoma niezbędnymi zmianami. Oryginalnym autorem algorytmu

jest Joachim Henke. Udostępnia on pliki źródłowe pod adresem

http://base91.sourceforge.net/

jako wolne oprogramowanie na licencji BSD.

 

Wyobraźmy sobie Portugalczyka, który nauczył się hiszpańskiego.

Nie było to szczególnie trudne. I ten oto Portugalczyk znalazł hiszpański

wiersz, który chciał przetłumaczyć na ojczysty język. No więc przetłumaczył.

Dosłownie lub używając najbliższych odpowiedników. Nie zgłębiał się w analizę

treści. Nie domyślał się ukrytych znaczeń parafraz, anafor, metafor i kalafior.

To tylko ciąg słów, które brzmią podobnie w obu językach. Nie było takiej

potrzeby. Porównał oryginał i tłumaczenie i po prostu stwierdził: "Działa? No

to działa. Nie próbuję się nawet zgadywać co podmiot liryczny miał na myśli".

 

I ja jestem tym Portugalczykiem. Przyznaję się bez bicia, że

BARDZO powierzchownie rozumiem logikę stojącą za tym algorytmem. Uważam, że

nie ma sensu wyważać otwartych drzwi. Ważne, że działa jak powinno działać.

 

OK, koniec tego dobrego. Oto główna pętla.

 

*/

 

temp=file_text_open_write(".encodetemp");

file_text_write_string(temp,d);

file_text_close(temp);

 

btemp=file_bin_open(".encodetemp",0);

encsize=file_bin_size(btemp);

 

for (bi=1; bi<=encsize; bi++)

{

byte[bi]=file_bin_read_byte(btemp);

}

 

for (i=1; i<=encsize; i++)

{

b |= byte << n;

n+=8;

 

if (n>13)

{

v = b & 8191;

 

if (v > 88)

{

b = b >> 13;

n-=13;

}

else

{

v = b & 16383;

b = b >> 14;

n -= 14;

}

o = o + global.b91_enc[v%91] + global.b91_enc[v/91];

}

}

if (n)

{

o = o + global.b91_enc[b%91];

 

if (n > 7 || b > 90)

o = o + global.b91_enc[b/91];

}

 

//Czas na zamknięcie i skaskowanie bardzo tymczasowego pliku

file_bin_close(btemp);

file_delete(".encodetemp");

 

return o;

 

<funç?o base91_decode>

GML
/*

Jeżeli w miarę ogarniasz mechanizm kodowania, spodoba ci się

dekodowanie. Obie funkcje mają dość podobną strukturę.

Jedyna różnica to to, że podczas dekodowania wszystko

dzieje się w drugą stronę.

 

Poniżej cała masa mądrze wyglądających zmiennych.

Wydają się znajome.

*/

 

d=argument[0];

l = string_length(d);

b=0;

n=0;

o="";

v=-1;

 

/*

Otwieramy bardzo tymczasowy plik binarny do zapisu.

*/

 

btemp=file_bin_open(".encodetemp",1);

 

 

/*

Autorem oryginalnego algorytmu jest Joachim Henke.

Udostępnia on basE91 na licencji BSD, pod adresem

http://base91.sourceforge.net.

 

W pętli znajdują się niezbędne zmiany naniesione przeze mnie

w tej implementacji.

*/

 

for (i = 1; i<=l; i++)

{

c=global.b91_dec[ord(string_char_at(d,i))];

 

if (v < 0)

v = c;

else

{

v+=c*91;

b |= v << n;

if (v & 8191 > 88)

n+=13;

else

n+=14;

 

while (n > 7)

do

{

file_bin_write_byte(btemp,(b & 255));

b = b >> 8;

n-=8;

}

 

v = -1;

}

}

 

if (v + 1)

file_bin_write_byte(btemp,chr((b | v << n) & 255));

 

/*

Zamkykamy plik otwarty binarnie i otwieramy go tekstowo

do odczytu.

*/

 

file_bin_close(btemp);

temp=file_text_open_read(".encodetemp");

 

ret=file_text_read_string(temp);

 

/*

Ponownie zamykamy plik otwarty tekstowo. Zamykamy go na dobre

i kasujemy.

*/

 

file_text_close(temp);

file_delete(".encodetemp");

 

return ret;

 

Plik gmx (LINK)

 

Powyższy przykład pozwoli kontynuować zaplanowany przeze mnie cykl dotyczący komunikacji pomiędzy GM Studio a serwerem HTTP. Dzięki temu stworzymy działającą grę, do której będzie można się zarejestrować, zalogować, uczestniczyć w rankinkach z innymi użytkownikami - a wszystko dzięki GML, językowi PHP oraz bazom danych MySQL.

 

I tutaj pojawia się moje pytanie: czy w związku z obecnością w przykładzie innych języków programowania niż GML - jednakże jedynie w stopniu umożliwiającym zobrazowanie przykładu - kontynuacja powinna odbywać się w dziale Inne języki?

 

Sugestie/poprawki/pytania/dyskusje/żale - zapraszam.

Odnośnik do komentarza
Udostępnij na innych stronach

Jeśli chcesz dodać odpowiedź, zaloguj się lub zarejestruj nowe konto

Jedynie zarejestrowani użytkownicy mogą komentować zawartość tej strony.

Zarejestruj nowe konto

Załóż nowe konto. To bardzo proste!

Zarejestruj się

Zaloguj się

Posiadasz już konto? Zaloguj się poniżej.

Zaloguj się
  • Ostatnio przeglądający   0 użytkowników

    • Brak zarejestrowanych użytkowników przeglądających tę stronę.
×
×
  • Dodaj nową pozycję...