[C++] [SFML] Une exception, et de source indescriptible

Le problème exposé dans ce sujet a été résolu.

Bonjour, Bonsoir, Bonjoir, je me présente, Salt Chaos. Je code un jeu en ce moment, qui aura pas le mérite de faire grand chose pour le moment. (Il s’inspirera de Pokémon Showdown pour ceux qui voient ce que c’est)

C’est concrètement là sur quoi je bosse, c’est encore assez vide, mais je pense qu’une image en tête, ce sera plus compréhensible. Le jeu est donc en C++, en utilisant le moteur SFML et… à un moment j’ai cette exception :

Bon j’imagine qu’il est vain de demander de l’aide si je ne vous passe pas le/les code qui font ça : Button.h

#pragma once
#include <SFML/Graphics.hpp>
#include <iostream>
using std::string;

class Button
{
public:
    Button();
    Button(string FileName);
    Button(string FileName, string name);
    ~Button();

    bool MouseOn(sf::Vector2i MousePos);
    void Draw(sf::RenderWindow *window);
    void SetPosition(int x, int y);
    void SetString(string text);
    sf::Sprite GetSprite();
    void SetSprite(string FileName);
    void SetFont(string FileName);
    void SetFont(sf::Font Font);
    float GetWidth();
    sf::Text Text;
    sf::Font Font;
protected:
    string TextUsed;
    

    sf::Texture TextureButton;
    sf::Sprite SpriteButt;
    double CurrentScale;
};

Button.cpp

#include "pch.h"
#include "Button.h"
using sf::Vector2i;
using std::cout;

Button::Button()
{

}
Button::Button(string FileName)
{
    if (!TextureButton.loadFromFile(FileName))
    {
        std::cout << "ERROR : " << FileName << ".png sprite Not Loaded";
    }

    SpriteButt.setTexture(TextureButton);
    SpriteButt.setPosition(sf::Vector2f(0, 0));
    SpriteButt.setOrigin(SpriteButt.getLocalBounds().width / 2, SpriteButt.getLocalBounds().height / 2);
    SpriteButt.setScale(1, 1);
    CurrentScale = 1;
}
Button::Button(string FileName, string name)
{
    if (!TextureButton.loadFromFile(FileName))
    {
        std::cout << "ERROR : " << FileName <<".png sprite Not Loaded";
    }

    SpriteButt.setTexture(TextureButton);
    SpriteButt.setPosition(sf::Vector2f(0, 0));
    SpriteButt.setOrigin(SpriteButt.getLocalBounds().width / 2, SpriteButt.getLocalBounds().height / 2);
    SpriteButt.setScale(1, 1);
    CurrentScale = 1;

    if (!Font.loadFromFile("Fipps.otf"))
    {
        std::cout << "ERROR : Button "<< name <<" Font not loaded \n";
    }
    Text.setFont(Font);
    Text.setCharacterSize(28);
    Text.setString(name);
    Text.setFillColor(sf::Color::Black);
    Text.setOrigin(Text.getLocalBounds().width / 2, Text.getLocalBounds().height / 2);
    Text.setPosition(SpriteButt.getPosition().x, SpriteButt.getPosition().y);
}

bool Button::MouseOn(Vector2i MousePos)
{
    int MouseX = MousePos.x;
    int MouseY = MousePos.y;
    int Button_Top = SpriteButt.getPosition().y - SpriteButt.getTextureRect().height / 2;
    int Button_Bot = SpriteButt.getPosition().y + SpriteButt.getTextureRect().height / 2;
    int Button_Left = SpriteButt.getPosition().x - SpriteButt.getTextureRect().width / 2;
    int Button_Right = SpriteButt.getPosition().x + SpriteButt.getTextureRect().width / 2;

    if (MouseX > Button_Left && MouseX < Button_Right && MouseY > Button_Top && MouseY < Button_Bot)
    {
        return true;
    }
    else return false;
}
void Button::SetPosition(int x, int y)
{
    SpriteButt.setPosition(sf::Vector2f(x, y));
    Text.setPosition(x, y);
}

void Button::SetString(string text)
{
    Text.setString(text);
    TextUsed = text;
}

float Button::GetWidth()
{
    return SpriteButt.getLocalBounds().width;
}
/*
void Button::SetSize(int x, int y)
{
    SpriteButt.setScale(0.5, 0.5);
    Text.setCharacterSize(14);
}
*/
void Button::Draw(sf::RenderWindow *window)
{
    window->draw(SpriteButt);
    window->draw(Text);
}

sf::Sprite Button::GetSprite()
{
    return SpriteButt;
}

void Button::SetSprite(string FileName)
{
    if (!TextureButton.loadFromFile(FileName))
    {
        std::cout << "ERROR : " << FileName << ".png sprite Not Loaded";
    }

    SpriteButt.setTexture(TextureButton);
    SpriteButt.setPosition(sf::Vector2f(0, 0));
    SpriteButt.setOrigin(SpriteButt.getLocalBounds().width / 2, SpriteButt.getLocalBounds().height / 2);
    SpriteButt.setScale(1, 1);
    CurrentScale = 1;
}

void Button::SetFont(string FileName)
{
    if (!Font.loadFromFile(FileName))
    {
        std::cout << "ERROR : " << FileName << " Font not loaded \n";
    }
    Text.setFont(Font);
}

void Button::SetFont(sf::Font Font)
{
    Text.setFont(Font);
}

Button::~Button()
{
}

Bon là y’a beaucoup de choses, mais l’exception n’apparait qu’à la méthode Draw(), à la ligne window->draw(Text);(ligne 89)

Cette méthode est elle même appelée, dans le Draw() d’une autre classe, donc seul Draw(), et peut-être Assign peuvent-être la source de l’exception. (Fin je pense, si vous voyez que c’est autres part, hésitez pas aussi) HeroDisplay.h

#pragma once
#include "Button.h"
#include "Character.h"
#include "Flame.h"
#include "ShovelKnight.h"
using std::vector;
using std::map;

class HeroDisplay :
    public Button
{
public:
    HeroDisplay();
    ~HeroDisplay();
    void Assign(int Hero);
    void Draw(sf::RenderWindow *window);
    void SetPosition(int x, int y);
    Button TypeDisplay1;
    Button TypeDisplay2;
    sf::Font Font;
private:
    Character *RepresentativeChar;
    int HNumbers;
    
    sf::Text NameDisplay;
    sf::Text SoulDisplay;
    sf::Texture Texture1;
    sf::Texture Texture2;
    
};

HeroDisplay.cpp

#include "pch.h"
#include "HeroDisplay.h"
using std::cout;

HeroDisplay::HeroDisplay() : Button("Hero Display.png")
{
    if (!Font.loadFromFile("Excellent.ttf"))
    {
        std::cout << "ERROR : HeroDisplay Font from the List not loaded \n";
    }
    NameDisplay.setFont(Font);
    NameDisplay.setCharacterSize(23);
    NameDisplay.setStyle(sf::Text::Bold);
    NameDisplay.setString("");
    NameDisplay.setLetterSpacing(0.6);
    NameDisplay.setFillColor(sf::Color::Black);
    NameDisplay.setOrigin(Text.getLocalBounds().width / 2, Text.getLocalBounds().height / 2);

    SoulDisplay.setFont(Font);
    SoulDisplay.setCharacterSize(23);
    SoulDisplay.setStyle(sf::Text::Bold);
    SoulDisplay.setString("");
    SoulDisplay.setLetterSpacing(0.6);
    SoulDisplay.setFillColor(sf::Color::Black);
    SoulDisplay.setOrigin(Text.getLocalBounds().width / 2, Text.getLocalBounds().height / 2);

    TypeDisplay1.SetString("");
    TypeDisplay2.SetString("");
}

void HeroDisplay::Assign(int Hero)
{
    switch (Hero)
    {
    case 0 :
        RepresentativeChar = new Flame();
        break;
    case 1 : 
        RepresentativeChar = new ShovelKnight();
        break;
    case 2 :
        RepresentativeChar;
        break;
    case 3:
        RepresentativeChar;
        break;
    case 4 :
        RepresentativeChar;
        break;
    case 5 :
        RepresentativeChar;
        break;
    case 6 :
        RepresentativeChar;
        break;
    case 7 :
        RepresentativeChar;
        break;
    case 8:
        RepresentativeChar;
        break;
    }
    NameDisplay.setString(RepresentativeChar->GetName());
    SoulDisplay.setString(RepresentativeChar->GetSoul());
    TypeDisplay1.SetSprite(RepresentativeChar->GetType1() + "TypeDisplay.png");
    TypeDisplay2.SetSprite(RepresentativeChar->GetType2() + "TypeDisplay.png");
    TypeDisplay1.SetString(RepresentativeChar->GetType1());
    TypeDisplay2.SetString(RepresentativeChar->GetType2());
    TypeDisplay1.SetFont(Font);
    TypeDisplay2.SetFont(Font);
}

void HeroDisplay::SetPosition(int x, int y)
{
    SpriteButt.setPosition(sf::Vector2f(x, y));
    NameDisplay.setPosition(sf::Vector2f(x-180, y-10));
    SoulDisplay.setPosition(sf::Vector2f(x + 180, y-10));
    TypeDisplay1.SetPosition(x + 180, y - 10);
}

void HeroDisplay::Draw(sf::RenderWindow *window)
{
    window->draw(SpriteButt);
    window->draw(NameDisplay);
    window->draw(SoulDisplay);
    TypeDisplay1.Draw(window);
}

HeroDisplay::~HeroDisplay()
{
}

Depuis que la ligne TypeDisplay1.Draw(window); (ligne 86) a été ajouté (et fait appel à Draw() de la classe Button) l’exception est apparu.

Concrétement ce sur quoi je bossais était juste des "boutons" sans la fonctionnalité de bouton (juste l’apparence) sensé s’afficher dans la colonne Types (que vous avez pu voir dans la screen du jeu que j’ai mis plus haut) qui peuvent être un ou deux (pour l’instant c’était sensé afficher qu’un des deux types pour chaque personnages).

Ce qui est le plus surprenant c’est que la seule différence par rapport au précédent boutons, c’est d’où ils prennent leurs texte qui est en leur centre, et leur position dans une autre classe.

N’hésitez pas à me demander plus de page de codes pour vous éclairer, j’comprend qu’avec juste deux pages c’est difficile à comprendre.

Merci d’avance pour votre aide.

SaltedStrategy.cpp (équivalent du main.cpp)

#include "pch.h"
#include <iostream>
#include <SFML/Graphics.hpp>
#include "Flame.h"
#include "ShovelKnight.h"
#include "Background.h"
#include "AttackButton.h"
#include "Button.h"
#include "TeamDisplayPart.h"
#include "CharDisplay.h"
#include "HeroNameDisplay.h"
#include "HeroOrAttackList.h"

using std::cout;
using sf::Mouse;
using sf::Event;
using sf::Clock;
using std::vector;

int main()
{
    sf::RenderWindow window(sf::VideoMode(1590, 860), "Salted Strategies", sf::Style::Default);
    cout << "Hello World!\n";
    /*Background Arena;
    Flame Flame1;
    ShovelKnight SK1;
    AttackButton FirstButton("Ignify");     //////Gameplay part
    */
    unsigned long long i = 0;
    Button Battle("Menu Button Red.png", "Battle");
    Battle.SetPosition(200, 200);
    Button Teams("Menu Button Red.png", "Teams");
    Teams.SetPosition(200, 300);
    Button Ladder("Menu Button Red.png", "Ladder");
    Ladder.SetPosition(200, 400);
    Button Options("Menu Button Red.png", "Options");
    Options.SetPosition(200, 500);

    int const Nbr_Characters_Max(8);
    vector<TeamDisplayPart> TeamDisplay(Nbr_Characters_Max);
    for (i = 0; i < TeamDisplay.size(); i++)
    {
        TeamDisplay[i].CutIn();
        TeamDisplay[i].SetPosition(70 + (TeamDisplay[0].GetWidth()*i), 100);
    }
    CharDisplay CharDisplay;
    CharDisplay.SetPosition(80, 207);
    HeroNameDisplay NameDisp;
    NameDisp.SetString("NomafuckJrol*uhagu");
    NameDisp.SetPosition(397, 175);
    HeroOrAttackList HAList;
    HAList.SetPosition(1283, 300);

    while (window.isOpen())
    {
        i++;
        sf::Vector2i MousePos = Mouse::getPosition(window);

        /*
        Flame1.CheckPV();
        SK1.CheckPV();
        Flame1.UpdateLifebar();
        SK1.UpdateLifebar();            //////Gameplay part
        */

        window.clear();////////////////////////////////////////////
        /*
        Battle.Draw(&window);
        Teams.Draw(&window);
        Ladder.Draw(&window);   ////////// Menu part
        Options.Draw(&window);
        */
        for (i = 0; i < TeamDisplay.size(); i++)
        {
            TeamDisplay[i].Draw(&window);
        }
        CharDisplay.Draw(&window);
        NameDisp.Draw(&window);
        HAList.Draw(&window);

        /*
        window.draw(Arena.Sprite);
        window.draw(Flame1.Sprite);
        window.draw(SK1.Sprite);
        window.draw(Flame1.Lifebar.BarOutline);
        window.draw(Flame1.Lifebar.Background);
        window.draw(Flame1.Lifebar.GreenBar);
        window.draw(Flame1.Lifebar.Numbers);        //////Gameplay part
        window.draw(SK1.Lifebar.BarOutline);
        window.draw(SK1.Lifebar.Background);
        window.draw(SK1.Lifebar.GreenBar);
        window.draw(SK1.Lifebar.Numbers);
        window.draw(FirstButton.SpriteButt);
        window.draw(FirstButton.Text);
        */
        window.display();//////////////////////////////////////////

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        Event event;
        while (window.pollEvent(event))
        {
            if (event.type == Event::Closed)
            {
                window.close();
            }
            if (event.type == Event::MouseButtonPressed && event.mouseButton.button == Mouse::Left && Teams.MouseOn(MousePos))
            {

            }
            for (int e = 0; e < TeamDisplay.size(); e++)
            {
                if (event.type == Event::MouseButtonPressed && event.mouseButton.button == Mouse::Left && TeamDisplay[e].MouseOn(MousePos))
                {
                    if (!TeamDisplay[e].GetSelected())
                    {
                        TeamDisplay[e].SetSelected(true);
                        for (int t = 0; t < TeamDisplay.size(); t++)
                        {
                            if (TeamDisplay[t].GetSelected() && t != e)
                            {
                                TeamDisplay[t].SetSelected(false);
                            }
                        }
                    }
                }
            }
        }


    }
}
``` Et faites pas trop gaffes au commentaires... celui là est un peu en bordel. J'imagine que je vais le ranger au fur et à mesure.
+0 -0

Hello.

Alors j’ai un peu fait de Windows et je ne me suis pas penché sur ton problème, mais quand je vois une adresse mémoire comme 0xCCCCCCD0, ça ressemble à un déréférencement de pointeur non initialisé.

T’aurais pas oublié de faire une initialisation quelque part ?

Je crois pas que ça puisse être ça, puisque le problème c’est que le seul pointeur qui ait un rapport ici c’est window, et il ne peut pas être vide, puisque la ligne d’avant il a fonctionné… (et plusieurs autres utilisation de la méthode avant l’utilisant aussi) Donc je pense pas que ce soit ça.

A priori dans le constructeur de HeroDisplay, tu appelles le constructeur de Button ainsi : Button("Hero Display.png").

Ce constructeur ne paramètre pas ta variable membre Text, ne charge pas non plus de Font pour ce texte, mais tu vas essayer de le draw quand même ; quelque chose me dit que c’est une bonne piste :)

+0 -0

Ca pourrait. Mais… Ouai, le meilleur moyen pour que vous compreniez est que je vous passe un autre fichier (que peut-être aurait-il fallu que je passe avant) Je tiens juste à préciser, effectivement c’est un bouton que j’essaye de dessiner mais, c’est ceux contenu dans HeroDisplay, et pas HeroDisplay lui même :

void HeroDisplay::Draw(sf::RenderWindow *window)
{
    window->draw(SpriteButt);
    window->draw(NameDisplay);
    window->draw(SoulDisplay);
    TypeDisplay1.Draw(window);
}

Ici HeroDisplay est un Button, mais il en contient deux autres qui sont TypeDisplay1 et TypeDisplay2, et c’est à leur utilisation que l’exception apparaît au niveau du texte. Alors qu’il est bien défini avant. Mais ça se voit dans ce fichier (que je ne vous ai pas encore passé) :

HeroOrAttackDisplay.h

#pragma once
#include "Button.h"
#include "HeroDisplay.h"
class HeroOrAttackList :
    public Button
{
public:
    HeroOrAttackList();
    ~HeroOrAttackList();

    void SetPosition(int x, int y);
    void Draw(sf::RenderWindow *window);
private:
    sf::Font HeaderFont;
    sf::Text IconeTextHeader;
    sf::Text NameTextHeader;
    sf::Text TypeTextHeader;
    sf::Text SoulTextHeader;
    vector<HeroDisplay> HeroList;
};

HeroOrAttackDisplay.cpp

#include "pch.h"
#include "HeroOrAttackList.h"
#include "HeroDisplay.h"
using std::cout;


HeroOrAttackList::HeroOrAttackList() : Button("Hero_Attack Display.png")
{
    if (!HeaderFont.loadFromFile("Neoletters.ttf"))
    {
        std::cout << "ERROR : Hero Or Attack List Font Header not loaded \n";
    }
    IconeTextHeader.setFont(HeaderFont);
    IconeTextHeader.setCharacterSize(30);
    IconeTextHeader.setString("Icone");
    IconeTextHeader.setFillColor(sf::Color::Black);
    IconeTextHeader.setOrigin(Text.getLocalBounds().width / 2, Text.getLocalBounds().height / 2);
    IconeTextHeader.setPosition(SpriteButt.getPosition().x, SpriteButt.getPosition().y);

    NameTextHeader.setFont(HeaderFont);
    NameTextHeader.setCharacterSize(30);
    NameTextHeader.setString("Name");
    NameTextHeader.setFillColor(sf::Color::Black);
    NameTextHeader.setOrigin(Text.getLocalBounds().width / 2, Text.getLocalBounds().height / 2);

    TypeTextHeader.setFont(HeaderFont);
    TypeTextHeader.setCharacterSize(30);
    TypeTextHeader.setString("Types");
    TypeTextHeader.setFillColor(sf::Color::Black);
    TypeTextHeader.setOrigin(Text.getLocalBounds().width / 2, Text.getLocalBounds().height / 2);

    SoulTextHeader.setFont(HeaderFont);
    SoulTextHeader.setCharacterSize(30);
    SoulTextHeader.setString("Soul");
    SoulTextHeader.setFillColor(sf::Color::Black);
    SoulTextHeader.setOrigin(Text.getLocalBounds().width / 2, Text.getLocalBounds().height / 2);
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////:
    /*HeroPossible["Flame"] = 0;
    HeroPossible["Shovel Knight"] = 1;
    HeroPossible["Eye of Cthulhu"] = 2;
    HeroPossible["Ruby Rose"] = 3;
    HeroPossible["Sodden Knight"] = 4;
    HeroPossible["Reunicleus"] = 5;
    HeroPossible["Ezmo"] = 6;
    HeroPossible["Asta"] = 7;
    HeroPossible["Slenderman"] = 8;
    */
    HeroList.resize(2);
    for (int i = 0; i < HeroList.size(); i++)
    {
        HeroList[i].Assign(i);
    }
}

void HeroOrAttackList::SetPosition(int x, int y)
{
    SpriteButt.setPosition(sf::Vector2f(x, y));
    IconeTextHeader.setPosition(x- SpriteButt.getLocalBounds().width/2+18, y - SpriteButt.getLocalBounds().height/2+5);
    NameTextHeader.setPosition(x - SpriteButt.getLocalBounds().width / 2 + 175, y - SpriteButt.getLocalBounds().height / 2 + 5);
    TypeTextHeader.setPosition(x - SpriteButt.getLocalBounds().width / 2 + 340, y - SpriteButt.getLocalBounds().height / 2 + 5);
    SoulTextHeader.setPosition(x - SpriteButt.getLocalBounds().width / 2 + 490, y - SpriteButt.getLocalBounds().height / 2 + 5);
    for (int i = 0; i < HeroList.size(); i++)
    {
        HeroList[i].SetPosition(x, y-SpriteButt.getLocalBounds().height/2+70+(40*i));
    }
}

void HeroOrAttackList::Draw(sf::RenderWindow *window)
{
    window->draw(SpriteButt);
    window->draw(IconeTextHeader);
    window->draw(NameTextHeader);
    window->draw(TypeTextHeader);
    window->draw(SoulTextHeader);
    for (int i = 0; i < HeroList.size(); i++)
    {
        HeroList[i].Draw(window);
    }
}

HeroOrAttackList::~HeroOrAttackList()
{
}

Histoire de réordonner la chose je précise : HeroOrAttackList est la grande fenêtre à droite (sur la screen du jeu), avec les header des colonnes, cette même classe qui contient plusieurs objets (autant qu’il faut) HeroDisplay pour chaque perso, qui lui même contient des textes et deux autres boutons TypeDisplay1 et TypeDisplay2, qui, ces derniers sont la source de mon problème.

Utilise ton debuger, et mets 2 points d’arrêts :

  • Juste après l’initialisation de ton bouton, pour voir sa valeur (est-il bien initialisé ?)
  • Juste avant ton appel à draw : Vérifie que la valeur est bien la bonne (identique que celle à l’initialisation).

Tu pourras ainsi vérifier que 1) ton bouton est bien initialisé, 2) il n’a pas été libéré incorrectement plus tôt.

Edit: Tu ne précises rien dans le constructeur de HeroDisplay comment initialiser tes boutons TypeDisplay1 et TypeDisplay2. Cela veut dire qu’ils utilisent le constructeur par défaut de bouton. Dans le constructeur par défaut de bouton, rien n’est indiqué pour initialiser une police (Font) ni le texte du bouton. Ce qui veut dire, que TypeDisplay n’est pas bien initialisé pour être affiché. Je suppose que le problème est la. C’est pourquoi 99% du temps il faut bien définir les constructeurs par défaut pour éviter ces problèmes (ici charger une police par défaut et un texte par défaut afin que le sf::text soit tout de même affichable).

Je rejouterai ça dans le constructeur par défaut de bouton par exemple (pas testé) :

if (!Font.loadFromFile("Fipps.otf"))
    {
        std::cout << "ERROR : Button "<< name <<" Font not loaded \n";
    }
    Text.setFont(Font);
    Text.setCharacterSize(28);
Text.setString("MonTextParDefaut");
+0 -0

Eh… j’ai beau avoir testé tout ça, en prenant même une initialisation plus poussée pour le constructeur par défaut… mais aucun changement, et le debugger m’indique que le texte est tout à fait normal. Fin… "sur la variable TextUsed". Car le Text de SFML n’a pas de variable std::string qui contient le string de la chaîne de caractère. Mais utilise une classe à lui sf::String, et qui n’est pas lisible comme un string normal. Ce qui fait que je peux effectivement dire en parallèle qu’ici :

void Button::SetString(string text)
{
    Text.setString(text);
    TextUsed = text;
}

Ce qui est un peu gênant pour déterminer si le texte dans celui ci à changé.

Après quelques minutes de réflexions : Bon ben merci en soi, sans toi j’aurait jamais trouvé, même si tu m’a pas donné la soluce. Je vais quand même donner la soluce à mon problème : C’était le Font. Concrétement malgré qu’il soit initialisé dans le constructeur par défaut comme tu m’a dit de faire, le problème n’avait pas changé. C’est que lorsque je faisais SetFont(Font) ça ne fonctionnait… qu’a moitié. L’attribut Font stocké dans HeroDisplay était bon, les autres textes en bénéficiant fonctionnait… Le problème se situait donc dans la méthode SetFont.

void Button::SetFont(sf::Font FFont)
{   
    //Font = FFont;
    Text.setFont(FFont);
}
void Button::SetFont(sf::Font FFont)
{   
    Font = FFont;
    Text.setFont(Font);
}

Le premier code c’est la version avec l’exception, le deuxième est la version qui marche. Pour un Sprite en SFML si la texture part (comme par exemple si c’est une variable locale et que la fin de la fonction arrive, la texture disparaît) le sprite ne fonctionne plus. Et j’ai eu le déclic, si c’était pareil avec les textes et les fonts… Semble-il que ce soit le cas. Lorsque j’utilisais SetFont, il lui mettait le font, pour la durée de la fonction… et n’en avait plus après, même celle par défaut.

Merci de votre aide ^^

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte