Torna indietro   Hardware Upgrade Forum > Software > Programmazione

Recensione realme 16 5G: lo smartphone con Selfie Mirror ha una batteria da 6550mAh
Recensione realme 16 5G: lo smartphone con Selfie Mirror ha una batteria da 6550mAh
realme 16 5G è un nuovo smartphone con sensore Sony IMX 852 da 50MP sul retro e uno specchio selfie fisico integrato nella camera bar, una prima nel segmento di mercato. Batteria da 6550mAh in un corpo da 8,1mm e 183g, certificazione IP69K e ricarica da 45W completano un pacchetto aggressivo per la fascia media, per uno dei prodotti più interessanti del produttore sul piano commerciale
Come rispettare tutte le nuove regole per i monopattini elettrici? La guida per non rischiare sanzioni
Come rispettare tutte le nuove regole per i monopattini elettrici? La guida per non rischiare sanzioni
Sono ormai definitive le nuove norme del Codice della Strada per i monopattini elettrici. Non solo targa e assicurazione, le regole sono tante e riguardano diversi aspetti, vi spieghiamo come evitare sanzioni che possono essere salate
DLSS 4.5: con Dynamic Frame Generation e MFG 6X NVIDIA alza la posta
DLSS 4.5: con Dynamic Frame Generation e MFG 6X NVIDIA alza la posta
DLSS 4.5 introduce Dynamic Multi Frame Generation e MFG 6X, permettendo fino a cinque frame generati per ogni frame renderizzato. I test su Cyberpunk 2077 e 007 First Light mostrano forti incrementi di FPS e riduzione della latenza su RTX 5090 Laptop. Migliorano fluidità, stabilità e qualità visiva.
Tutti gli articoli Tutte le news

Vai al Forum
Rispondi
 
Strumenti
Old 09-12-2007, 09:07   #1
IceCoder
Member
 
Iscritto dal: Dec 2007
Messaggi: 121
[C++] Malloc() & Spazio di allocazione

Ho un problema con malloc():

sto lavorando ad un progetto di grafica tridimensionale e devo caricare un modello 3D.
uso questa struttura:

Quote:
#pragma pack(1)
typedef struct TAGcobHEADER
{
char Ident[3];
int nMeshes;
int nMaterials;
int FirstFrame;
int LastFrame;
int TicksPerFrame;
int FrameSpeed;
} cobHEADER;

typedef struct TAGcobVECTOR
{
float x;
float y;
float z;
} cobVECTOR;

typedef struct TAGcobMATERIAL
{
float ambR, ambG, ambB;
float diffR, diffG, diffB;
float specR, specG, specB;
float shine;
char tex[64];

} cobMATERIAL;
typedef struct TAGcobMESH
{
int nVertices;
int nFaces;
float posX, posY, posZ;
float rotX, rotY, rotZ;
float scalX, scalY, scalZ;
int useMaterial; //int as bool
int MatID;
cobVECTOR *VertexList;
int *FaceList;
} cobMESH;

#pragma pack()

class cobFILE
{
public:
cobHEADER Header;
cobMESH *MeshList;
cobMATERIAL *MaterialList;
int LoadModel(char *fname);
cobFILE();
cobFILE(char *fname);
~cobFILE();

};
Quando devo caricare la mesh in *MeshList creo un array di mesh ed in seguito per ogni mesh creo un array di vettori ed uno di 'int' per le facce.

Il problema è che dopo un totale di spazio allocato con malloc() ho un crash alla prima chiamata della medesima funzione...

Non capisco quale sia il problema... Perchè non posso creare molti puntatori?
Eppure lo spazio utilizzato dal modello di prova è realmente esiguo (poco meno di 320 bytes)

Posto di seguito la funzione per il caricamento del modello:

Quote:
int cobFILE::LoadModel(char *fname)
{
FILE *f;
f = fopen(fname, "r+");
int meshes, mats;
int meshi = 0;
int mati = 0;

if(!f)
return 1; //FILE NOT FOUND
//READ HEADER
fread(&this->Header, sizeof(cobHEADER), 1, f);

if(this->Header.Ident[0] != 'C' || this->Header.Ident[1] != 'O' || this->Header.Ident[2] != 'B')
{
fclose(f);
return -1; //THIS IS NOT A VALID FORMAT
}

//ALLOCATE MEMORY
meshes = this->Header.nMeshes;
mats = this->Header.nMaterials;
if(meshes > 0)
{
this->MeshList = (cobMESH *)malloc(sizeof(cobMESH) * meshes);
//memset(this->MeshList, 0, sizeof(cobMESH) * meshes);
}

if(mats > 0)
{
this->MaterialList = (cobMATERIAL *)malloc(sizeof(cobMATERIAL) * mats);
//memset(this->MaterialList, 0, sizeof(cobMATERIAL) * mats);
}

//READ MODEL
while(!feof(f))
{
char chunkID = 0;
fread(&chunkID, sizeof(char), 1, f);
switch(chunkID)
{
case cob_MESH: //IT IS A MODEL!
{
int i;
cobMESH *msh = (cobMESH *)((long)this->MeshList + (long)(sizeof(cobMESH) * meshi));
if(msh == NULL)
{
fclose(f);
return -2; //ERROR IN ALLOCATING MEMORY STORAGE
}
msh->FaceList = (int *)malloc(sizeof(int) * msh->nFaces * 3);
msh->VertexList = (cobVECTOR *)malloc(sizeof(cobVECTOR) * msh->nVertices);

fread(&msh->nVertices, sizeof(int), 1, f);
fread(&msh->nFaces, sizeof(int), 1, f);
fread(&msh->posX, sizeof(float), 1, f);
fread(&msh->posY, sizeof(float), 1, f);
fread(&msh->posZ, sizeof(float), 1, f);
fread(&msh->rotX, sizeof(float), 1, f);
fread(&msh->rotY, sizeof(float), 1, f);
fread(&msh->rotZ, sizeof(float), 1, f);
fread(&msh->scalX, sizeof(float), 1, f);
fread(&msh->scalY, sizeof(float), 1, f);
fread(&msh->scalZ, sizeof(float), 1, f);
fread(&msh->useMaterial, sizeof(int), 1, f);
fread(&msh->MatID, sizeof(int), 1, f);
for(i = 0; i < msh->nVertices; i++)
{
cobVECTOR *v;
float x, y, z;
fread(&x, sizeof(float), 1, f);
fread(&y, sizeof(float), 1, f);
fread(&z, sizeof(float), 1, f);
v = (cobVECTOR *)((long)msh->VertexList + (long)(sizeof(cobVECTOR) * i));
v->x = x;
v->y = y;
v->z = z;
_asm{ nop }

}





for(i = 0; i < msh->nFaces * 3; i++)
{
int *v;
int x;
fread(&x, sizeof(int), 1, f);
v = (int *)((long)msh->FaceList + (long)(sizeof(int) * i));
*(v) = x;
_asm{ nop }

}



}


}
}

fclose(f);
return 0; //MODEL LOADED
}
IceCoder è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 09:25   #2
kingmastermind
Junior Member
 
Iscritto dal: May 2007
Messaggi: 29
Vediamo se ci azzecco!

All'inizio del programma, a mio parere, dopo avere definito le "class" e le "struct" devono essere dichiarate le variabili "complesse" che poi utilizzerai in cobFILE

cobMESH *this->MeshList;
cobMATERIAL *this->MaterialList;
...... eccetera
sinceramente nel tuo listato non le ho viste inizializzate! correggimi se sbaglio ! Spero di esserti stato di aiuto!
kingmastermind è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 09:33   #3
IceCoder
Member
 
Iscritto dal: Dec 2007
Messaggi: 121
quelle le inizializza il costruttore richiamando memset()

il punto è che dopo che il modello è stato caricato io avvio glut e richiamando glutCreateWindow() il programma crasha e il debugger mi riporta al sorgente di malloc

mi succede sempre, è la terza volta che ricomincio da zero tutto il progetto pensando di aver tralasciato qualcosa.. eppure il problema è sempre di malloc()

ho esaminato altri sorgenti per caricare modelli 3D ma non trovo grandi differenze, anzi il mio modello mi risulta anche meno esteso di altri che allocano decine di puntatori.

Mi è venuto il dubbio che sia un problema di heap ma alloco meno di 320 bytes!

Ho chiesto anke in altri forum ma nessuno riesce a rispondermi
IceCoder è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 09:38   #4
cionci
Senior Member
 
L'Avatar di cionci
 
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
Prima di tutto: perché usi malloc invece di new ?
cionci è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 09:49   #5
IceCoder
Member
 
Iscritto dal: Dec 2007
Messaggi: 121
Quote:
Originariamente inviato da cionci Guarda i messaggi
Prima di tutto: perché usi malloc invece di new ?
ti giuro: se mi avessi risposto 2 mesi fa ti sarei uscito dal lettore floppy per ringraziarti

ho risolto in questo modo proprio qlk secondo fa asd

grazie 1000

ma...gia che ci siamo...sapete perchè malloc() ha un limite di allocazione?
IceCoder è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 09:49   #6
kingmastermind
Junior Member
 
Iscritto dal: May 2007
Messaggi: 29
Ritento!

Sinceramente non capisco come possa il costruttore inizializzare una variabile complessa e non dichiarata con memset. Atteso che questo avvenga (memset non lo uso mai!) , guarda che quelle variabili, se il programma è scrittto in C++, il programma le utilizza solo ed esclusivamente in ambito funzione cobFILE(), mentre non ti da accesso ad esse da altre funzioni e va in crash, ecco perchè ti consiglio di dichiararle come variabili globali all'inizio del programma! Se questo lo hai già fatto allora non so come aiutarti davvero ! Ciao !
kingmastermind è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 09:52   #7
IceCoder
Member
 
Iscritto dal: Dec 2007
Messaggi: 121
Quote:
Originariamente inviato da kingmastermind Guarda i messaggi
Sinceramente non capisco come possa il costruttore inizializzare una variabile complessa e non dichiarata con memset. Atteso che questo avvenga (memset non lo uso mai!) , guarda che quelle variabili, se il programma è scrittto in C++, il programma le utilizza solo ed esclusivamente in ambito funzione cobFILE(), mentre non ti da accesso ad esse da altre funzioni e va in crash, ecco perchè ti consiglio di dichiararle come variabili globali all'inizio del programma! Se questo lo hai già fatto allora non so come aiutarti davvero ! Ciao !
se intendi allocarle lo faceva malloc() ora sostituito da new

ma era memset() che le inizializzava a zero.
IceCoder è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 09:55   #8
cionci
Senior Member
 
L'Avatar di cionci
 
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
Quote:
Originariamente inviato da kingmastermind Guarda i messaggi
ecco perchè ti consiglio di dichiararle come variabili globali all'inizio del programma!
Variabili globali in C++ ?
Quote:
Originariamente inviato da IceCoder Guarda i messaggi
ti giuro: se mi avessi risposto 2 mesi fa ti sarei uscito dal lettore floppy per ringraziarti

ho risolto in questo modo proprio qlk secondo fa asd

grazie 1000

ma...gia che ci siamo...sapete perchè malloc() ha un limite di allocazione?
In teoria il problema potrebbe esserci ancora o magari è rimasto nascosto, anche perché non sono a conoscenza di limiti di malloc rispetto a new. Magari avranno una diversa politica di allocazione e questo rende il problema, almeno pero ora, non visibile.

Ultima modifica di cionci : 09-12-2007 alle 09:58.
cionci è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 10:00   #9
cionci
Senior Member
 
L'Avatar di cionci
 
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
Quello che noto è che ci sono mooooolti problemi strutturali nel codice. Stai programmando in C++ "alla C". C++ è un linguaggio ad oggetti.
Ad esempio l'information hiding impone di non rendere pubbliche le variabili membro.
In ogni caso quella classe fa troppe cose...fa sia da repository che da loader.
Prima di tutto farei una buona riorganizzazione del codice e poi dopo penserei a ricercare quel bug.
cionci è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 10:02   #10
kingmastermind
Junior Member
 
Iscritto dal: May 2007
Messaggi: 29
Danger !

Egregio moderatore, scusa se sono stato un poco bricconcello nell'esprimermi, eppure qualcosa ne capisco anche io di C++ ! questo è il mio sito, con i miei programmi, dagli un occhiata ! forse di C++ ne capisco abbastanza anche io!

http://www.winturtle.netsons.org/
kingmastermind è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 10:02   #11
IceCoder
Member
 
Iscritto dal: Dec 2007
Messaggi: 121
Quote:
Originariamente inviato da cionci Guarda i messaggi
Variabili globali in C++ ?
beh in effetti direi che è improbabile che un programmatore usi le globali in c++ scrivendo un motore grafico visto e considerato che il numero di modelli è indefinito e poi ogni livello contiene milioni di vertici e decine di oggetti di tipo differente (oggetti mobili, materiali, luci, statici, listati di animazioni, matrici, ecc...) sarebbe un po proibitivo.
IceCoder è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 10:08   #12
IceCoder
Member
 
Iscritto dal: Dec 2007
Messaggi: 121
ah forse ho capito cosa intendi.. se intendi strutture globali e funzioni che operano sulle strutture anzicchè classi che contengono funzioni e strutture puo anche essere utilizzabile come metodo però quando lavori con collisioni, suoni, animazioni e compagnia bella c'è da uscire pazzi O.O

EDIT: scusate il doppio post
IceCoder è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 10:12   #13
cionci
Senior Member
 
L'Avatar di cionci
 
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
Quote:
Originariamente inviato da kingmastermind Guarda i messaggi
Egregio moderatore, scusa se sono stato un poco bricconcello nell'esprimermi, eppure qualcosa ne capisco anche io di C++ ! questo è il mio sito, con i miei programmi, dagli un occhiata ! forse di C++ ne capisco abbastanza anche io!
Guarda non voglio assolutamente insinuare che tu non sappia il C++, sto solo dicendo che le variabili globali vanno contro praticamente tutte le regole dell'ingegneria del software Pensa che molti dei programmatori che conosco evitano anche di usare il design pattern singleton perché a loro modo dire è come buttare fuori le variabili globali dalla porta e reinserirle dalla finestra

Al massimo posso capire che possa essere una variabile globale l'istanza della classe che rappresenta l'engine grafico (ma personalmente nemmeno quello lo metterei globali) in un'applicazione 3D, ma già i repository di mesh e materiali devono essere contenuti all'interno dell'engine stesso e non essere globali.
cionci è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 10:15   #14
kingmastermind
Junior Member
 
Iscritto dal: May 2007
Messaggi: 29
Tutto fa brodo! La tua è una buona idea!

Mi scuso, se sono stato poco chiaro prima, per variabile globale io intendo qualsiasi variabile inizializzata all'inizio del programma (es. unsigned char x; ), che può essere tranquillamente utilizzata da qualsiasi funzione! Purtroppo il C++ permette ad esempio di dichiarare la varilabile x sia all'inizio del programma, sia in una qualsiasi funzione o procedura che tu crei (es. cobFILE)! Risultato: bisogna stare attenti poi a verificare quale abbia precedenza tra le due nella singola funzione o nella Main()! E' un casino! ed allora preferisco sempre dichiararle all'inizio e non ri-utilizzarle assolutamente nelle funzioni! Le variabili globali di cui tu parli sono più incasinate e sta a te vedere se utilizzarle o meno!
kingmastermind è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 10:31   #15
cionci
Senior Member
 
L'Avatar di cionci
 
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
IceCoder: la classe cobFILE ha troppe responsabilità. Fa sia da loader che da repository.
Quindi una organizzazione minima alternativa potrebbe essere questa.
Codice:
class cobFileLoader
{
   //per ora possiamo fare un method object
   CobFileLoader();

   //metodi privati per suddividere le varie operazioni
   static int loadHeader(ifstream &file, cobHeader &header);
   static int loadMeshes(ifstream &file, cobMeshes &meshes);
   static int loadMaterials(ifstream &file, cobMaterials &materials);
public:
   static int Load(string fileName, cobHeader &header, cobMaterials &materials, cobMeshes &meshes);
};

class cobObject
{
   cobHeader header;
   cobMaterials materials;
   cobMeshes meshes;
   AbsolutePosition position;

   cobObject(); //costruttore di default privato
public:
   cobObject(const cobObject &object); //costruttore di copia per copiare gli oggetti
   //factory method: dovrà essere chiamato da chi vuole costruire l'oggetto
   static cobObject LoadFromCobFile(const string &fileName);
   //ora non so cosa vuoi fare con questi dati, ma ad esempio:
   void moveBy(float dx, float dy, float dz);
   void moveTo(AbsolutePosition p);
   codHeader & getHeader();
   cobMaterials & getMaterials();
   cobMeshes & getMeshes();
   AbsolutePosition & getAbsolutePosition();   
}

//per completezza implemento il factory method
static cobObject cobObject::LoadFromCobFile(const string &fileName)
{
    cobObject obj;
    
    if(!cobFileLoader::Load(fileName, obj.header, obj.materials, obj.meshes))
   {
       return NULL; 
   }
   //qui fa uso del costruttore di copia, ma si potrebbe anche evitare per migliorare le prestazioni,
   //ma come si dice: early optimization is the root of all evil ;)
   return obj; 
}
cobHeader header;
cobMaterials materials;
cobMeshes meshes;

L'header può essere anche la struttura dati che hai messo sopra, in quanto è una struttura statica.

cobMaterials e cobMeshes per me devono essere collection. Ad esempio vector delle strutture sopra (nel modo più semplice);

Ultima modifica di cionci : 09-12-2007 alle 10:44.
cionci è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 10:39   #16
IceCoder
Member
 
Iscritto dal: Dec 2007
Messaggi: 121
Quote:
Originariamente inviato da cionci Guarda i messaggi
IceCoder: la classe cobFILE ha troppe responsabilità. Fa sia da loader che da repository.
Quindi una organizzazione minima alternativa potrebbe essere questa.
Codice:
class CobFileLoader
{
   //per ora possiamo fare un method object
   CobFileLoader();
public:
   static int Load(cobHeader &header, cobMaterials &materials, cobMeshes &meshes);
};

class cobObject
{
   cobHeader header;
   cobMaterials materials;
   cobMeshes meshes;
   float x, y, z;

   cobObject(); //costruttore di default privato
public:
   cobObject(const obObject &object); //costruttore di copia per copiare gli oggetti
   //factory method: dovrà essere chiamato da chi vuole costruire l'oggetto
   static cobObject & LoadFromCobFile(const string &fileName);
   //ora non so cosa vuoi fare con questi dati, ma ad esempio:
   void moveBy(float dx, float dy, float dz);
   void moveTo(AbsolutePosition p);
   codHeader & getHeader();
   cobMaterials & getMaterials();
   cobMeshes & getMeshes();
   AbsolutePosition & getAbsolutePosition();   
}
cobHeader header;
cobMaterials materials;
cobMeshes meshes;

L'header può essere anche la struttura dati che hai messo sopra, in quanto è una struttura statica.

cobMaterials e cobMeshes per me devono essere collection. Ad esempio vector delle strutture sopra (nel modo più semplice);
beh al momento è solo una classe di prova, sto lavorando ad un estensione (*.cob) mia personale, quindi con l'andare del tempo la perfezionerò. al momento devo solo caricare un modello e fare il rendering per vedere se il programma di conversione funziona bene :P
IceCoder è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 10:42   #17
cionci
Senior Member
 
L'Avatar di cionci
 
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
Quote:
Originariamente inviato da IceCoder Guarda i messaggi
beh al momento è solo una classe di prova, sto lavorando ad un estensione (*.cob) mia personale, quindi con l'andare del tempo la perfezionerò. al momento devo solo caricare un modello e fare il rendering per vedere se il programma di conversione funziona bene :P
Ho fatto qualche modifica. In ogni caso è bene organizzare anche le prove in modo corretto...e c'è un motivo: se l'avessi organizzato in modo corretto probabilmente non avresti avuto quell'errore
cionci è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2007, 10:49   #18
IceCoder
Member
 
Iscritto dal: Dec 2007
Messaggi: 121
Quote:
Originariamente inviato da cionci Guarda i messaggi
Ho fatto qualche modifica. In ogni caso è bene organizzare anche le prove in modo corretto...e c'è un motivo: se l'avessi organizzato in modo corretto probabilmente non avresti avuto quell'errore
gia, è vero ^^
IceCoder è offline   Rispondi citando il messaggio o parte di esso
 Rispondi


Recensione realme 16 5G: lo smartphone con Selfie Mirror ha una batteria da 6550mAh Recensione realme 16 5G: lo smartphone con Selfi...
Come rispettare tutte le nuove regole per i monopattini elettrici? La guida per non rischiare sanzioni Come rispettare tutte le nuove regole per i mono...
DLSS 4.5: con Dynamic Frame Generation e MFG 6X NVIDIA alza la posta DLSS 4.5: con Dynamic Frame Generation e MFG 6X ...
Plaud NotePin S, il registratore IA si fa indossabile (ma è facile da perdere) Plaud NotePin S, il registratore IA si fa indoss...
Redmi Watch 6 in prova: lo smartwatch con ampio display da 2000 nit a meno di 100 euro Redmi Watch 6 in prova: lo smartwatch con ampio ...
La NASA spiega perché non ci sono...
La NASA sta provando il Divergent Deploy...
Fidanzarsi con l'IA non è cos&igr...
AI Overviews: quando il riassunto &egrav...
Il circuito segreto di Apple finisce a W...
Disastro Meta: l'azienda elimina il rico...
Google Gemini non va: centinaia di segna...
Neural Dawn mostra il futuro del gaming ...
Telegram torna su Apple Watch con un'app...
Da oggi si può acquistare Amazon ...
Windows 11 è più veloce: implementati il...
Ritornano gli auricolari con il cavo: Te...
Insta360 Luna Ultra: ecco il debutto del...
BOOX Go 6 Gen II ufficiale: ora si scriv...
BYD sfida Tesla con un piano da 2 miliar...
Chromium
GPU-Z
OCCT
LibreOffice Portable
Opera One Portable
Opera One 106
CCleaner Portable
CCleaner Standard
Cpu-Z
Driver NVIDIA GeForce 546.65 WHQL
SmartFTP
Trillian
Google Chrome Portable
Google Chrome 120
VirtualBox
Tutti gli articoli Tutte le news Tutti i download

Strumenti

Regole
Non Puoi aprire nuove discussioni
Non Puoi rispondere ai messaggi
Non Puoi allegare file
Non Puoi modificare i tuoi messaggi

Il codice vB è On
Le Faccine sono On
Il codice [IMG] è On
Il codice HTML è Off
Vai al Forum


Tutti gli orari sono GMT +1. Ora sono le: 03:16.


Powered by vBulletin® Version 3.6.4
Copyright ©2000 - 2026, Jelsoft Enterprises Ltd.
Served by www3v