From 203a56879fe56c494d020a4bf5ae4d18345684ed Mon Sep 17 00:00:00 2001 From: Fazri Gading Date: Fri, 23 Jan 2026 16:20:40 +0800 Subject: [PATCH 1/9] Update release.yml --- .github/workflows/release.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4e09ae1..53eed05 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -86,6 +86,10 @@ jobs: permissions: contents: write + env: + CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }} + DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }} + steps: # FIX: './package.json' Module Not Found in `Get version` step - name: Checkout code @@ -117,4 +121,4 @@ jobs: generate_release_notes: true draft: true # DYNAMIC FLAGS: Mark as pre-release ONLY IF it's NOT a tag (meaning it's a branch push) - prerelease: ${{ github.ref_type != 'tag' }} \ No newline at end of file + prerelease: ${{ github.ref_type != 'tag' }} From 34143d987271bb46308d8df70740b46090e35788 Mon Sep 17 00:00:00 2001 From: Fazri Gading Date: Fri, 23 Jan 2026 21:30:27 +0800 Subject: [PATCH 2/9] patch v2.0.11-beta: fix env issue in GA release, warn Intel Mac users, add com templates. (#115) * fix: throw error for Intel Mac user * docs: first draft of issue and PR template * fix: env of curseforge API key and discord client ID --- .github/CODE_OF_CONDUCT.md | 83 ++++++++++++++++++ .github/CONTRIBUTING.md | 70 ++++++++++++++++ .../ISSUE_TEMPLATE/assets_contribution.yml | 54 ++++++++++++ .github/ISSUE_TEMPLATE/bug_report.yml | 84 +++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.yml | 42 ++++++++++ .../ISSUE_TEMPLATE/security_vulnerability.yml | 61 ++++++++++++++ .github/ISSUE_TEMPLATE/support_request.yml | 54 ++++++++++++ .../ISSUE_TEMPLATE/translation_request.yml | 42 ++++++++++ .github/PULL_REQUEST_TEMPLATE/bug_fix.md | 24 ++++++ .../PULL_REQUEST_TEMPLATE/documentation.md | 16 ++++ .github/PULL_REQUEST_TEMPLATE/hotfix.md | 26 ++++++ .github/PULL_REQUEST_TEMPLATE/localization.md | 20 +++++ .github/PULL_REQUEST_TEMPLATE/new_feature.md | 25 ++++++ .github/PULL_REQUEST_TEMPLATE/refactor.md | 27 ++++++ .github/SECURITY.md | 55 ++++++++++++ backend/managers/gameManager.js | 5 ++ 16 files changed, 688 insertions(+) create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/assets_contribution.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/security_vulnerability.yml create mode 100644 .github/ISSUE_TEMPLATE/support_request.yml create mode 100644 .github/ISSUE_TEMPLATE/translation_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE/bug_fix.md create mode 100644 .github/PULL_REQUEST_TEMPLATE/documentation.md create mode 100644 .github/PULL_REQUEST_TEMPLATE/hotfix.md create mode 100644 .github/PULL_REQUEST_TEMPLATE/localization.md create mode 100644 .github/PULL_REQUEST_TEMPLATE/new_feature.md create mode 100644 .github/PULL_REQUEST_TEMPLATE/refactor.md create mode 100644 .github/SECURITY.md diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..8d6ac55 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,83 @@ +# Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..4ca0d3f --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,70 @@ +# Contributing to Hytale F2P + +Thank you for your interest in contributing to Hytale F2P! We welcome contributions from everyone. By participating in this project, you agree to abide by our [Code of Conduct](CODE_OF_CONDUCT.md). + +## How to Contribute + +### Reporting Bugs + +- Use the [Bug Report](.github/ISSUE_TEMPLATE/bug_report.yml) template +- Include as much detail as possible +- Include screenshots if applicable +- Check if the issue has already been reported + +### Suggesting Features + +- Use the [Feature Request](.github/ISSUE_TEMPLATE/feature_request.yml) template +- Clearly describe the feature and its benefits +- Consider if the feature aligns with the project's goals + +### Contributing Code + +1. Fork the repository +2. Create a feature branch: `git checkout -b feature/your-feature-name` +3. Make your changes +4. Write tests if applicable +5. Ensure all tests pass +6. Update documentation if needed +7. Commit your changes: `git commit -m 'Add some feature'` +8. Push to the branch: `git push origin feature/your-feature-name` +9. Submit a pull request + +### Pull Request Process + +- Use the appropriate [Pull Request template](.github/PULL_REQUEST_TEMPLATE/) +- Ensure your PR description clearly describes the changes +- Link to any related issues +- Wait for review and address any feedback + +## Development Setup + +1. Clone the repository: `git clone https://github.com/your-username/hytale-f2p.git` +2. Install dependencies: `npm install` (or appropriate command) +3. Set up your development environment +4. Run tests: `npm test` +5. Start development server: `npm run dev` + +## Code Style + +- Follow the existing code style in the project +- Use meaningful variable and function names +- Write clear, concise comments +- Keep functions small and focused + +## Testing + +- Write unit tests for new features +- Ensure all existing tests pass +- Test on multiple platforms/browsers if applicable + +## Documentation + +- Update README.md if needed +- Document new features or changes +- Keep documentation up to date + +## Questions? + +If you have questions about contributing, feel free to ask in our [Discussions](https://github.com/your-username/hytale-f2p/discussions) or create a [Support Request](.github/ISSUE_TEMPLATE/support_request.yml). + +Thank you for contributing to Hytale F2P! \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/assets_contribution.yml b/.github/ISSUE_TEMPLATE/assets_contribution.yml new file mode 100644 index 0000000..4d3652f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/assets_contribution.yml @@ -0,0 +1,54 @@ +name: Asset Contribution +description: Contribute assets (images, sounds, models, etc.) +title: "[ASSETS] " +labels: ["assets"] +body: + - type: textarea + id: description + attributes: + label: Asset Description + description: Describe the asset(s) you're contributing. + placeholder: "What type of asset is this? What does it represent?" + validations: + required: true + + - type: input + id: format + attributes: + label: File Format + description: What format are the asset files in? + placeholder: "e.g. PNG, JPG, MP3, OBJ" + + - type: input + id: license + attributes: + label: License + description: What license applies to this asset? + placeholder: "e.g. CC0, MIT, Public Domain" + + - type: textarea + id: usage + attributes: + label: Intended Usage + description: Where and how should this asset be used in the project? + placeholder: "This asset should be used for..., in the following context..." + + - type: textarea + id: source + attributes: + label: Source/Attribution + description: If this asset is derived from another source, provide attribution. + placeholder: "Created by me, or derived from [source]" + + - type: input + id: link + attributes: + label: Download Link + description: Provide a link to download or view the asset. + placeholder: "GitHub release, Google Drive, etc." + + - type: textarea + id: additional + attributes: + label: Additional Information + description: Any other information about the asset. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..6fa5672 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,84 @@ +name: Bug Report +description: Create a report to help us improve +title: "[BUG] " +labels: ["bug"] +body: + - type: textarea + id: description + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. + placeholder: "Tell us what you see! The more detail the better." + validations: + required: true + + - type: textarea + id: reproduce + attributes: + label: To Reproduce + description: Steps to reproduce the behavior + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen. + validations: + required: true + + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: If applicable, add screenshots to help explain your problem. + + - type: input + id: version + attributes: + label: Version + description: What version of the project are you running? + placeholder: "e.g. v1.2.3" + validations: + required: true + + - type: dropdown + id: os + attributes: + label: Operating System + description: What operating system are you using? + options: + - Windows + - macOS + - Linux + - iOS + - Android + - Other + validations: + required: true + + - type: dropdown + id: browser + attributes: + label: Browser (if applicable) + description: What browser are you using? + options: + - Chrome + - Firefox + - Safari + - Edge + - Opera + - Other + - N/A + + - type: textarea + id: additional + attributes: + label: Additional context + description: Add any other context about the problem here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..89b6e49 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,42 @@ +name: Feature Request +description: Suggest an idea for this project +title: "[FEATURE] " +labels: ["enhancement"] +body: + - type: textarea + id: summary + attributes: + label: Summary + description: Brief explanation of the feature. + placeholder: "Describe in a few sentences what this feature would do." + validations: + required: true + + - type: textarea + id: problem + attributes: + label: Is your feature request related to a problem? Please describe. + description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + placeholder: "Ex. I'm always frustrated when [...]" + + - type: textarea + id: solution + attributes: + label: Describe the solution you'd like + description: A clear and concise description of what you want to happen. + placeholder: "Describe what you want to happen." + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Describe alternatives you've considered + description: A clear and concise description of any alternative solutions or features you've considered. + placeholder: "Describe any alternative solutions or features you've considered." + + - type: textarea + id: additional + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/security_vulnerability.yml b/.github/ISSUE_TEMPLATE/security_vulnerability.yml new file mode 100644 index 0000000..2337e04 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/security_vulnerability.yml @@ -0,0 +1,61 @@ +name: Security Vulnerability +description: Report a security vulnerability +title: "[SECURITY] " +labels: ["security"] +body: + - type: markdown + attributes: + value: | + Thank you for reporting a security vulnerability. Please review our [Security Policy](SECURITY.md) for more information on how we handle security issues. + + If you are reporting a security vulnerability, please provide as much detail as possible so we can assess and address it promptly. + + - type: textarea + id: summary + attributes: + label: Summary + description: Brief description of the security issue. + placeholder: "Describe the security vulnerability in a few sentences." + validations: + required: true + + - type: textarea + id: details + attributes: + label: Vulnerability Details + description: Detailed description of the vulnerability, including how it can be exploited. + placeholder: "Provide detailed steps, code snippets, or other information that demonstrates the vulnerability." + validations: + required: true + + - type: textarea + id: impact + attributes: + label: Impact + description: What is the potential impact of this vulnerability? + placeholder: "Describe the potential consequences if this vulnerability is exploited." + validations: + required: true + + - type: textarea + id: mitigation + attributes: + label: Suggested Mitigation + description: Any suggestions for fixing or mitigating the issue. + placeholder: "Provide any suggestions for how to fix or mitigate this vulnerability." + + - type: input + id: contact + attributes: + label: Contact Information (Optional) + description: How can we contact you for more information? + placeholder: "Email address or other contact method" + + - type: checkboxes + id: terms + attributes: + label: Terms + description: By submitting this issue, you agree to our responsible disclosure terms. + options: + - label: I understand that this is a private security report and will not publicly disclose details until the issue is resolved. + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/support_request.yml b/.github/ISSUE_TEMPLATE/support_request.yml new file mode 100644 index 0000000..1b03828 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/support_request.yml @@ -0,0 +1,54 @@ +name: Support Request +description: Request help or support +title: "[SUPPORT] " +labels: ["support"] +body: + - type: textarea + id: question + attributes: + label: What do you need help with? + description: Describe your question or issue clearly. + placeholder: "I'm having trouble with..." + validations: + required: true + + - type: textarea + id: context + attributes: + label: Context + description: Provide any relevant context or background information. + placeholder: "I've tried..., I expected..., but got..." + + - type: input + id: version + attributes: + label: Version + description: What version are you using? + placeholder: "e.g. v1.2.3" + + - type: dropdown + id: platform + attributes: + label: Platform + description: What platform are you using? + options: + - Windows + - macOS + - Linux + - iOS + - Android + - Web Browser + - Other + + - type: textarea + id: logs + attributes: + label: Logs or Error Messages + description: If applicable, paste any error messages or logs here. + render: shell + + - type: textarea + id: additional + attributes: + label: Additional Information + description: Any other information that might help us assist you. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/translation_request.yml b/.github/ISSUE_TEMPLATE/translation_request.yml new file mode 100644 index 0000000..fc43333 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/translation_request.yml @@ -0,0 +1,42 @@ +name: Translation Request +description: Request translation for text or content +title: "[TRANSLATION] " +labels: ["translation"] +body: + - type: input + id: language + attributes: + label: Target Language + description: What language do you want to translate to? + placeholder: "e.g. Spanish (es-ES), French (fr-FR)" + validations: + required: true + + - type: textarea + id: source_text + attributes: + label: Source Text + description: The original text that needs to be translated. + placeholder: "Paste the text here..." + validations: + required: true + + - type: textarea + id: context + attributes: + label: Context + description: Provide context about where this text appears or how it's used. + placeholder: "This text appears in..., It's used for..." + + - type: input + id: file_location + attributes: + label: File Location + description: Where is this text located in the codebase? + placeholder: "e.g. src/components/Button.js:15" + + - type: textarea + id: notes + attributes: + label: Additional Notes + description: Any specific instructions or notes for the translator. \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE/bug_fix.md b/.github/PULL_REQUEST_TEMPLATE/bug_fix.md new file mode 100644 index 0000000..badbb0d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/bug_fix.md @@ -0,0 +1,24 @@ +## Description +Brief description of the bug fix. + +## Related Issue +Fixes # (issue number) + +## Changes Made +- List the changes made to fix the bug +- Be specific about what was changed and why + +## Testing +- How did you test the fix? +- What scenarios were covered? + +## Screenshots (if applicable) +Add screenshots to demonstrate the fix. + +## Checklist +- [ ] My code follows the project's style guidelines +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE/documentation.md b/.github/PULL_REQUEST_TEMPLATE/documentation.md new file mode 100644 index 0000000..2eda0fe --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/documentation.md @@ -0,0 +1,16 @@ +## Description +Brief description of the documentation changes. + +## Related Issue +Addresses # (issue number) + +## Changes Made +- List the documentation files that were added, updated, or removed +- Describe what information was added or corrected + +## Checklist +- [ ] Documentation is clear and easy to understand +- [ ] Links and references are correct +- [ ] Code examples (if any) are accurate and functional +- [ ] Spelling and grammar are correct +- [ ] Documentation follows the project's style guidelines \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE/hotfix.md b/.github/PULL_REQUEST_TEMPLATE/hotfix.md new file mode 100644 index 0000000..0e54f46 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/hotfix.md @@ -0,0 +1,26 @@ +## Description +Brief description of the hotfix. + +## Related Issue +Fixes # (issue number) - URGENT + +## Changes Made +- List the minimal changes made to fix the critical issue +- Be specific about what was changed + +## Urgency +Why is this a hotfix? (Critical bug, security issue, production down, etc.) + +## Testing +- How was the hotfix tested? +- What was the minimal testing performed? + +## Deployment Notes +- Any special deployment considerations? +- Rollback plan if needed? + +## Checklist +- [ ] This is a minimal change addressing only the critical issue +- [ ] No new features or unrelated changes included +- [ ] Basic functionality verified +- [ ] Ready for immediate deployment \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE/localization.md b/.github/PULL_REQUEST_TEMPLATE/localization.md new file mode 100644 index 0000000..3cf0748 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/localization.md @@ -0,0 +1,20 @@ +## Description +Brief description of the localization changes. + +## Related Issue +Addresses # (issue number) + +## Changes Made +- List the languages and files that were updated +- Describe what text was translated or updated + +## Languages Updated +- Language 1 (locale code) +- Language 2 (locale code) + +## Checklist +- [ ] Translations are accurate and culturally appropriate +- [ ] Placeholder variables (%s, %d, etc.) are preserved +- [ ] Text length is appropriate for UI elements +- [ ] No hardcoded strings remain +- [ ] Localization files are properly formatted \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE/new_feature.md b/.github/PULL_REQUEST_TEMPLATE/new_feature.md new file mode 100644 index 0000000..0ab1bec --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/new_feature.md @@ -0,0 +1,25 @@ +## Description +Brief description of the new feature. + +## Related Issue +Addresses # (issue number) + +## Changes Made +- List the changes made to implement the feature +- Be specific about new files, modified files, and functionality added + +## Testing +- How did you test the new feature? +- What scenarios were covered? + +## Screenshots (if applicable) +Add screenshots to demonstrate the new feature. + +## Checklist +- [ ] My code follows the project's style guidelines +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] I have updated the documentation accordingly \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE/refactor.md b/.github/PULL_REQUEST_TEMPLATE/refactor.md new file mode 100644 index 0000000..0969f79 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/refactor.md @@ -0,0 +1,27 @@ +## Description +Brief description of the refactoring changes. + +## Related Issue +Addresses # (issue number) + +## Changes Made +- List the refactored code sections +- Describe what was improved (readability, performance, maintainability, etc.) + +## Motivation +Why was this refactoring necessary? + +## Impact +- Does this change affect any APIs or interfaces? +- Are there any breaking changes? + +## Testing +- How was the refactored code tested? +- Did existing tests pass? + +## Checklist +- [ ] Code is more readable and maintainable +- [ ] No functionality was broken +- [ ] Performance was not negatively impacted +- [ ] All existing tests pass +- [ ] New tests were added if necessary \ No newline at end of file diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..395f3ff --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,55 @@ +# Security Policy + +## Supported Versions + +We take security seriously. The following versions of our project are currently being supported with security updates: + +| Version | Supported | +| ------- | ------------------ | +| 1.x.x | :white_check_mark: | +| < 1.0 | :x: | + +## Reporting a Vulnerability + +If you discover a security vulnerability, please report it to us as follows: + +**Do not report security vulnerabilities through public GitHub issues.** + +Instead, please report security vulnerabilities by: + +1. Using the [Security Vulnerability Report](.github/ISSUE_TEMPLATE/security_vulnerability.yml) template (this creates a private issue) +2. Emailing [security@yourdomain.com](mailto:security@yourdomain.com) (if available) +3. Contacting the maintainers directly through secure channels + +## What to Include in Your Report + +Please include the following information in your report: + +- A clear description of the vulnerability +- Steps to reproduce the issue +- Potential impact of the vulnerability +- Any suggested fixes or mitigations +- Your contact information for follow-up + +## Our Response Process + +1. **Acknowledgment**: We will acknowledge receipt of your report within 48 hours +2. **Investigation**: We will investigate the issue and work on a fix +3. **Updates**: We will provide regular updates on our progress +4. **Resolution**: Once fixed, we will notify you and publicly disclose the issue (with your permission) + +## Responsible Disclosure + +We kindly ask that you: + +- Give us reasonable time to fix the issue before public disclosure +- Avoid accessing or modifying user data +- Avoid denial-of-service attacks or other disruptive actions + +## Recognition + +We appreciate security researchers who help keep our project safe. With your permission, we will acknowledge your contribution in our security advisories. + +## Questions? + +If you have questions about our security policy, please contact us through the methods listed above. \ No newline at end of file diff --git a/backend/managers/gameManager.js b/backend/managers/gameManager.js index 2d5e4e0..7d4245a 100644 --- a/backend/managers/gameManager.js +++ b/backend/managers/gameManager.js @@ -13,6 +13,11 @@ const { resolveJavaPath, detectSystemJava, downloadJRE, getJavaExec, getBundledJ async function downloadPWR(version = 'release', fileName = '4.pwr', progressCallback, cacheDir = CACHE_DIR) { const osName = getOS(); const arch = getArch(); + + if (osName === 'darwin' && arch === 'amd64') { + throw new Error('Hytale x86_64 Intel Mac Support has not been released yet. Please check back later.'); + } + const url = `https://game-patches.hytale.com/patches/${osName}/${arch}/${version}/0/${fileName}`; const dest = path.join(cacheDir, fileName); From a2e2d5e5fdd1951c003a1622e6e0918dbee8ba6a Mon Sep 17 00:00:00 2001 From: Fazri Gading Date: Fri, 23 Jan 2026 22:10:35 +0800 Subject: [PATCH 3/9] implemented late patch should be in #115 --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 53eed05..727c57a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,10 @@ on: - 'v*' workflow_dispatch: +env: + CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }} + DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }} + jobs: build-linux: runs-on: ubuntu-latest @@ -86,10 +90,6 @@ jobs: permissions: contents: write - env: - CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }} - DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }} - steps: # FIX: './package.json' Module Not Found in `Get version` step - name: Checkout code From 2a87acfe46d9327e06d72fbfc05dc018eaf78a4f Mon Sep 17 00:00:00 2001 From: Fazri Gading Date: Fri, 23 Jan 2026 22:46:31 +0800 Subject: [PATCH 4/9] Final patch for release.yml v2.0.11 --- .github/workflows/release.yml | 36 +++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 727c57a..405e76d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,10 +8,6 @@ on: - 'v*' workflow_dispatch: -env: - CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }} - DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }} - jobs: build-linux: runs-on: ubuntu-latest @@ -29,6 +25,14 @@ jobs: cache: 'npm' - run: npm ci + - name: Create .env file + env: + CF_KEY: ${{ secrets.CURSEFORGE_API_KEY }} + DISCORD_ID: ${{ secrets.DISCORD_CLIENT_ID }} + run: | + echo "CURSEFORGE_API_KEY=$CF_KEY" > .env + echo "DISCORD_CLIENT_ID=$DISCORD_ID" >> .env + - name: Build Linux Packages run: | npx electron-builder --linux --x64 --arm64 --publish never @@ -52,7 +56,17 @@ jobs: node-version: '22' cache: 'npm' - run: npm ci - - run: npx electron-builder --win --publish never + + - name: Create .env file + env: + CF_KEY: ${{ secrets.CURSEFORGE_API_KEY }} + DISCORD_ID: ${{ secrets.DISCORD_CLIENT_ID }} + run: | + echo "CURSEFORGE_API_KEY=$CF_KEY" > .env + echo "DISCORD_CLIENT_ID=$DISCORD_ID" >> .env + + - name: Build Windows Packages + run: npx electron-builder --win --publish never - uses: actions/upload-artifact@v4 with: name: windows-builds @@ -70,7 +84,17 @@ jobs: node-version: '22' cache: 'npm' - run: npm ci - - run: npx electron-builder --mac --publish never + + - name: Create .env file + env: + CF_KEY: ${{ secrets.CURSEFORGE_API_KEY }} + DISCORD_ID: ${{ secrets.DISCORD_CLIENT_ID }} + run: | + echo "CURSEFORGE_API_KEY=$CF_KEY" > .env + echo "DISCORD_CLIENT_ID=$DISCORD_ID" >> .env + + - name: Build Windows Packages + run: npx electron-builder --mac --publish never - uses: actions/upload-artifact@v4 with: name: macos-builds From ea21fb15d633b51f443ae32d32c1ab7e5de11564 Mon Sep 17 00:00:00 2001 From: Fazri Gading Date: Sun, 25 Jan 2026 05:18:22 +0800 Subject: [PATCH 5/9] fix: JRE retry button --- GUI/index.html | 14 +- GUI/js/ui.js | 296 +++++++++++++++++++++++++------- GUI/style.css | 6 + backend/managers/gameManager.js | 8 + backend/managers/javaManager.js | 51 +++++- backend/utils/fileManager.js | 13 +- main.js | 102 +++++++---- 7 files changed, 386 insertions(+), 104 deletions(-) diff --git a/GUI/index.html b/GUI/index.html index 2cb204b..fa94706 100644 --- a/GUI/index.html +++ b/GUI/index.html @@ -619,9 +619,17 @@
- +
+ + + +
diff --git a/GUI/js/ui.js b/GUI/js/ui.js index b09399e..44182cb 100644 --- a/GUI/js/ui.js +++ b/GUI/js/ui.js @@ -10,6 +10,8 @@ let progressErrorContainer; let progressErrorMessage; let progressRetryInfo; let progressRetryBtn; +let progressJRRetryBtn; +let progressPWRRetryBtn; // Download retry state let currentDownloadState = { @@ -199,7 +201,8 @@ function updateProgress(data) { if ((data.error || (data.message && data.message.includes('failed'))) && !(data.retryState && data.retryState.isAutomaticRetry)) { const errorType = categorizeError(data.message); - showDownloadError(data.message, data.canRetry, errorType); + console.log('[UI] Showing download error:', { message: data.message, canRetry: data.canRetry, errorType }); + showDownloadError(data.message, data.canRetry, errorType, data); } else if (data.percent === 100) { hideDownloadError(); } else if (data.retryState && data.retryState.isAutomaticRetry) { @@ -230,9 +233,17 @@ function updateRetryState(retryState) { } } -function showDownloadError(errorMessage, canRetry = true, errorType = 'general') { - if (!progressErrorContainer || !progressErrorMessage || !progressRetryBtn) return; +function showDownloadError(errorMessage, canRetry = true, errorType = 'general', data = null) { + if (!progressErrorContainer || !progressErrorMessage) return; + console.log('[UI] showDownloadError called with:', { errorMessage, canRetry, errorType, data }); + console.log('[UI] Data properties:', { + hasData: !!data, + hasRetryData: !!(data && data.retryData), + dataErrorType: data && data.errorType, + dataIsJREError: data && data.retryData && data.retryData.isJREError + }); + currentDownloadState.lastError = errorMessage; currentDownloadState.canRetry = canRetry; currentDownloadState.errorType = errorType; @@ -242,13 +253,37 @@ function showDownloadError(errorMessage, canRetry = true, errorType = 'general') currentDownloadState.branch = data.retryData.branch; currentDownloadState.fileName = data.retryData.fileName; currentDownloadState.cacheDir = data.retryData.cacheDir; + // Override errorType if specified in data + if (data.errorType) { + currentDownloadState.errorType = data.errorType; + } } + // Hide all retry buttons first + if (progressRetryBtn) progressRetryBtn.style.display = 'none'; + if (progressJRRetryBtn) progressJRRetryBtn.style.display = 'none'; + if (progressPWRRetryBtn) progressPWRRetryBtn.style.display = 'none'; + // User-friendly error messages const userMessage = getErrorMessage(errorMessage, errorType); progressErrorMessage.textContent = userMessage; progressErrorContainer.style.display = 'block'; - progressRetryBtn.style.display = canRetry ? 'block' : 'none'; + + // Show appropriate retry button based on error type + if (canRetry) { + if (errorType === 'jre') { + if (progressJRRetryBtn) { + console.log('[UI] Showing JRE retry button'); + progressJRRetryBtn.style.display = 'block'; + } + } else { + // All other errors use PWR retry button (game download, butler, etc.) + if (progressPWRRetryBtn) { + console.log('[UI] Showing PWR retry button'); + progressPWRRetryBtn.style.display = 'block'; + } + } + } // Add visual indicators based on error type progressErrorContainer.className = `progress-error-container error-${errorType}`; @@ -261,6 +296,11 @@ function showDownloadError(errorMessage, canRetry = true, errorType = 'general') function hideDownloadError() { if (!progressErrorContainer) return; + // Hide all retry buttons + if (progressRetryBtn) progressRetryBtn.style.display = 'none'; + if (progressJRRetryBtn) progressJRRetryBtn.style.display = 'none'; + if (progressPWRRetryBtn) progressPWRRetryBtn.style.display = 'none'; + progressErrorContainer.style.display = 'none'; currentDownloadState.canRetry = false; currentDownloadState.lastError = null; @@ -589,6 +629,8 @@ function setupUI() { progressErrorMessage = document.getElementById('progressErrorMessage'); progressRetryInfo = document.getElementById('progressRetryInfo'); progressRetryBtn = document.getElementById('progressRetryBtn'); + progressJRRetryBtn = document.getElementById('progressJRRetryBtn'); + progressPWRRetryBtn = document.getElementById('progressPWRRetryBtn'); // Setup draggable progress bar setupProgressDrag(); @@ -784,6 +826,8 @@ function categorizeError(message) { return 'space'; } else if (msg.includes('conflict') || msg.includes('already exists')) { return 'conflict'; + } else if (msg.includes('jre') || msg.includes('java runtime')) { + return 'jre'; } else { return 'general'; } @@ -812,6 +856,8 @@ function getErrorMessage(technicalMessage, errorType) { return 'Insufficient disk space. Free up space and retry.'; case 'conflict': return 'Installation directory conflict. Please retry.'; + case 'jre': + return 'Java runtime download failed. Please retry.'; default: return 'Download failed. Please retry.'; } @@ -839,70 +885,192 @@ function updateConnectionQuality(quality) { // Enhanced retry button setup function setupRetryButton() { - if (!progressRetryBtn) return; - - progressRetryBtn.addEventListener('click', async () => { - if (!currentDownloadState.canRetry || currentDownloadState.isDownloading) { - return; - } - - // Disable retry button during retry - progressRetryBtn.disabled = true; - progressRetryBtn.textContent = '🔄 Retrying...'; - progressRetryBtn.classList.add('retrying'); - currentDownloadState.isDownloading = true; - - try { - // Hide error state during retry - hideDownloadError(); - - // Reset retry info styling for manual retries - if (progressRetryInfo) { - progressRetryInfo.style.background = ''; - progressRetryInfo.style.color = ''; - } - - // Update progress text with context-aware message - if (progressText) { - const contextMessage = getRetryContextMessage(); - progressText.textContent = contextMessage; + // Setup JRE retry button + if (progressJRRetryBtn) { + progressJRRetryBtn.addEventListener('click', async () => { + if (!currentDownloadState.canRetry || currentDownloadState.isDownloading) { + return; } + progressJRRetryBtn.disabled = true; + progressJRRetryBtn.textContent = 'Retrying...'; + progressJRRetryBtn.classList.add('retrying'); + currentDownloadState.isDownloading = true; - // Ensure retry data exists, create defaults if null - if (!currentDownloadState.retryData) { - currentDownloadState.retryData = { - branch: 'release', - fileName: '4.pwr' - }; - console.log('[UI] Created default retry data:', currentDownloadState.retryData); - } - - // Send retry request to backend - if (window.electronAPI && window.electronAPI.retryDownload) { - const result = await window.electronAPI.retryDownload(currentDownloadState.retryData); + try { + hideDownloadError(); - if (!result.success) { - throw new Error(result.error || 'Retry failed'); + if (progressRetryInfo) { + progressRetryInfo.style.background = ''; + progressRetryInfo.style.color = ''; + } + + if (progressText) { + progressText.textContent = 'Re-downloading Java runtime...'; } - } else { - // Fallback for development/testing - console.warn('electronAPI.retryDownload not available, simulating retry...'); - await new Promise(resolve => setTimeout(resolve, 2000)); - throw new Error('Retry API not available'); - } - } catch (error) { - console.error('Retry failed:', error); - const errorType = categorizeError(error.message); - showDownloadError(`Retry failed: ${error.message}`, true, errorType); - - // Reset retry button - progressRetryBtn.disabled = false; - progressRetryBtn.textContent = '🔄 Retry Download'; - progressRetryBtn.classList.remove('retrying'); - currentDownloadState.isDownloading = false; - } - }); + if (!currentDownloadState.retryData || currentDownloadState.errorType !== 'jre') { + currentDownloadState.retryData = { + isJREError: true, + jreUrl: '', + fileName: 'jre.tar.gz', + cacheDir: '', + osName: 'linux', + arch: 'amd64' + }; + console.log('[UI] Created default JRE retry data:', currentDownloadState.retryData); + } + + if (window.electronAPI && window.electronAPI.retryDownload) { + const result = await window.electronAPI.retryDownload(currentDownloadState.retryData); + if (!result.success) { + throw new Error(result.error || 'JRE retry failed'); + } + } else { + console.warn('electronAPI.retryDownload not available, simulating JRE retry...'); + await new Promise(resolve => setTimeout(resolve, 2000)); + throw new Error('JRE retry API not available'); + } + + } catch (error) { + console.error('JRE retry failed:', error); + showDownloadError(`JRE retry failed: ${error.message}`, true, 'jre'); + } finally { + if (progressJRRetryBtn) { + progressJRRetryBtn.disabled = false; + progressJRRetryBtn.textContent = 'Retry Java Download'; + progressJRRetryBtn.classList.remove('retrying'); + } + currentDownloadState.isDownloading = false; + } + }); + } + + // Setup PWR retry button + if (progressPWRRetryBtn) { + progressPWRRetryBtn.addEventListener('click', async () => { + if (!currentDownloadState.canRetry || currentDownloadState.isDownloading) { + return; + } + progressPWRRetryBtn.disabled = true; + progressPWRRetryBtn.textContent = 'Retrying...'; + progressPWRRetryBtn.classList.add('retrying'); + currentDownloadState.isDownloading = true; + + try { + hideDownloadError(); + + if (progressRetryInfo) { + progressRetryInfo.style.background = ''; + progressRetryInfo.style.color = ''; + } + + if (progressText) { + const contextMessage = getRetryContextMessage(); + progressText.textContent = contextMessage; + } + + if (!currentDownloadState.retryData || currentDownloadState.errorType === 'jre') { + currentDownloadState.retryData = { + branch: 'release', + fileName: '4.pwr' + }; + console.log('[UI] Created default PWR retry data:', currentDownloadState.retryData); + } + + if (window.electronAPI && window.electronAPI.retryDownload) { + const result = await window.electronAPI.retryDownload(currentDownloadState.retryData); + if (!result.success) { + throw new Error(result.error || 'Game retry failed'); + } + } else { + console.warn('electronAPI.retryDownload not available, simulating PWR retry...'); + await new Promise(resolve => setTimeout(resolve, 2000)); + throw new Error('Game retry API not available'); + } + + } catch (error) { + console.error('PWR retry failed:', error); + const errorType = categorizeError(error.message); + showDownloadError(`Game retry failed: ${error.message}`, true, errorType, error); + } finally { + if (progressPWRRetryBtn) { + progressPWRRetryBtn.disabled = false; + progressPWRRetryBtn.textContent = error && error.isJREError ? 'Retry Java Download' : 'Retry Game Download'; + progressPWRRetryBtn.classList.remove('retrying'); + } + currentDownloadState.isDownloading = false; + } + }); + } + + // Setup generic retry button (fallback) + if (progressRetryBtn) { + progressRetryBtn.addEventListener('click', async () => { + if (!currentDownloadState.canRetry || currentDownloadState.isDownloading) { + return; + } + progressRetryBtn.disabled = true; + progressRetryBtn.textContent = 'Retrying...'; + progressRetryBtn.classList.add('retrying'); + currentDownloadState.isDownloading = true; + + try { + hideDownloadError(); + + if (progressRetryInfo) { + progressRetryInfo.style.background = ''; + progressRetryInfo.style.color = ''; + } + + if (progressText) { + const contextMessage = getRetryContextMessage(); + progressText.textContent = contextMessage; + } + + if (!currentDownloadState.retryData) { + if (currentDownloadState.errorType === 'jre') { + currentDownloadState.retryData = { + isJREError: true, + jreUrl: '', + fileName: 'jre.tar.gz', + cacheDir: '', + osName: 'linux', + arch: 'amd64' + }; + } else { + currentDownloadState.retryData = { + branch: 'release', + fileName: '4.pwr' + }; + } + console.log('[UI] Created default retry data:', currentDownloadState.retryData); + } + + if (window.electronAPI && window.electronAPI.retryDownload) { + const result = await window.electronAPI.retryDownload(currentDownloadState.retryData); + if (!result.success) { + throw new Error(result.error || 'Retry failed'); + } + } else { + console.warn('electronAPI.retryDownload not available, simulating retry...'); + await new Promise(resolve => setTimeout(resolve, 2000)); + throw new Error('Retry API not available'); + } + + } catch (error) { + console.error('Retry failed:', error); + const errorType = categorizeError(error.message); + showDownloadError(`Retry failed: ${error.message}`, true, errorType); + } finally { + if (progressRetryBtn) { + progressRetryBtn.disabled = false; + progressRetryBtn.textContent = 'Retry Download'; + progressRetryBtn.classList.remove('retrying'); + } + currentDownloadState.isDownloading = false; + } + }); + } } function getRetryContextMessage() { @@ -925,6 +1093,8 @@ function getRetryContextMessage() { return 'Retrying with corrected permissions...'; case 'conflict': return 'Retrying after resolving conflicts...'; + case 'jre': + return 'Re-downloading Java runtime...'; default: return 'Initiating retry download...'; } diff --git a/GUI/style.css b/GUI/style.css index 8e492f1..8e28ef5 100644 --- a/GUI/style.css +++ b/GUI/style.css @@ -1815,6 +1815,12 @@ body { gap: 0.75rem; } +.progress-retry-buttons { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} + .progress-retry-info { color: #fbbf24; font-family: 'JetBrains Mono', monospace; diff --git a/backend/managers/gameManager.js b/backend/managers/gameManager.js index 3087147..2fb8b62 100644 --- a/backend/managers/gameManager.js +++ b/backend/managers/gameManager.js @@ -520,9 +520,17 @@ async function installGame(playerName = 'Player', progressCallback, javaPathOver try { await downloadJRE(progressCallback, customCacheDir, customJreDir); } catch (error) { + // Don't immediately fall back to system Java for JRE download errors - let user retry + if (error.isJREError) { + console.error('[Install] JRE download failed, allowing user retry:', error.message); + throw error; // Re-throw JRE errors to trigger retry UI + } + + // For non-download JRE errors, fall back to system Java const fallback = await detectSystemJava(); if (fallback) { javaBin = fallback; + console.log('[Install] Using system Java as fallback'); } else { throw error; } diff --git a/backend/managers/javaManager.js b/backend/managers/javaManager.js index c7b48ac..84af570 100644 --- a/backend/managers/javaManager.js +++ b/backend/managers/javaManager.js @@ -9,7 +9,7 @@ const tar = require('tar'); const { expandHome, JRE_DIR } = require('../core/paths'); const { getOS, getArch } = require('../utils/platformUtils'); const { loadConfig } = require('../core/config'); -const { downloadFile } = require('../utils/fileManager'); +const { downloadFile, retryDownload } = require('../utils/fileManager'); const execFileAsync = promisify(execFile); const JAVA_EXECUTABLE = 'java' + (process.platform === 'win32' ? '.exe' : ''); @@ -188,6 +188,20 @@ async function getJavaDetection() { }; } +// Manual retry function for JRE downloads +async function retryJREDownload(url, cacheFile, progressCallback) { + console.log('Initiating manual JRE retry...'); + + // Ensure cache directory exists before retrying + const cacheDir = path.dirname(cacheFile); + if (!fs.existsSync(cacheDir)) { + console.log('Creating JRE cache directory:', cacheDir); + fs.mkdirSync(cacheDir, { recursive: true }); + } + + return await retryDownload(url, cacheFile, progressCallback); +} + async function downloadJRE(progressCallback, cacheDir, jreDir = JRE_DIR) { if (!fs.existsSync(cacheDir)) { fs.mkdirSync(cacheDir, { recursive: true }); @@ -230,7 +244,40 @@ async function downloadJRE(progressCallback, cacheDir, jreDir = JRE_DIR) { progressCallback('Fetching Java runtime...', null, null, null, null); } console.log('Fetching Java runtime...'); - await downloadFile(platform.url, cacheFile, progressCallback); + let jreFile; + try { + jreFile = await downloadFile(platform.url, cacheFile, progressCallback); + + // If downloadFile returns false or undefined, it means the download failed + // We should retry the download with a manual retry + if (!jreFile || typeof jreFile !== 'string') { + console.log('[JRE Download] JRE file download failed or incomplete, attempting retry...'); + jreFile = await retryJREDownload(platform.url, cacheFile, progressCallback); + } + + // Double-check we have a valid file + if (!jreFile || typeof jreFile !== 'string') { + throw new Error(`JRE download failed: received invalid path ${jreFile}. Please retry download.`); + } + + } catch (downloadError) { + console.error('[JRE Download] JRE download failed:', downloadError.message); + + // Enhance error with retry information for the UI + const enhancedError = new Error(`JRE download failed: ${downloadError.message}`); + enhancedError.originalError = downloadError; + enhancedError.canRetry = downloadError.isConnectionLost ? false : (downloadError.canRetry !== false); + enhancedError.jreUrl = platform.url; + enhancedError.jreDest = cacheFile; + enhancedError.osName = osName; + enhancedError.arch = arch; + enhancedError.fileName = fileName; + enhancedError.cacheDir = cacheDir; + enhancedError.isJREError = true; // Flag to identify JRE errors + enhancedError.isConnectionLost = downloadError.isConnectionLost || false; + + throw enhancedError; + } console.log('Download finished'); } diff --git a/backend/utils/fileManager.js b/backend/utils/fileManager.js index 576cc96..972ca61 100644 --- a/backend/utils/fileManager.js +++ b/backend/utils/fileManager.js @@ -423,7 +423,8 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) { const canRetry = (error.canRetry === false) ? false : isRetryable; if (!canRetry || attempt === maxRetries - 1) { - retryState.canRetry = false; + // Don't set retryState.canRetry to false for max retries - user should still be able to retry manually + retryState.canRetry = error.canRetry === false ? false : true; console.error(`Non-retryable error or max retries reached: ${error.code || error.message}`); break; } @@ -439,6 +440,9 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) { enhancedError.retryState = retryState; enhancedError.lastError = lastError; enhancedError.detailedError = detailedError; + + // Allow manual retry unless it's a connection lost error + enhancedError.canRetry = !lastError?.isConnectionLost && lastError?.canRetry !== false; throw enhancedError; } @@ -543,6 +547,13 @@ async function retryDownload(url, dest, progressCallback, previousError = null) additionalRetries = Math.max(2, 5 - previousError.retryState.attempts); } + // Ensure cache directory exists before retrying + const destDir = path.dirname(dest); + if (!fs.existsSync(destDir)) { + console.log('Creating cache directory:', destDir); + fs.mkdirSync(destDir, { recursive: true }); + } + try { await downloadFile(url, dest, progressCallback, additionalRetries); console.log('Manual retry successful'); diff --git a/main.js b/main.js index 7e67543..e7e2767 100644 --- a/main.js +++ b/main.js @@ -3,6 +3,7 @@ require('dotenv').config({ path: path.join(__dirname, '.env') }); const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron'); const fs = require('fs'); const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveChatUsername, loadChatUsername, saveChatColor, loadChatColor, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, saveLanguage, loadLanguage, saveCloseLauncherOnStart, loadCloseLauncherOnStart, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched } = require('./backend/launcher'); +const { retryPWRDownload } = require('./backend/managers/gameManager'); const logger = require('./backend/logger'); const profileManager = require('./backend/managers/profileManager'); @@ -430,37 +431,41 @@ ipcMain.handle('install-game', async (event, playerName, javaPath, installPath, console.log('[Main] Returning success response for install-game:', successResponse); return successResponse; } catch (error) { - console.error('Install error:', error); + // console.error('Install error:', error); const errorMessage = error.message || error.toString(); // Enhanced error data extraction for both download and Butler errors let errorData = { message: errorMessage, error: true, - canRetry: true, + canRetry: true, // Default to true, will be overridden by specific error props retryData: null }; + // Prioritize JRE errors first + if (error.isJREError) { + console.log('[Main] Processing JRE download error with retry context'); + errorData.retryData = { + isJREError: true, + jreUrl: error.jreUrl, + fileName: error.fileName, + cacheDir: error.cacheDir, + osName: error.osName, + arch: error.arch + }; + // For JRE errors, allow manual retry unless explicitly disabled + errorData.canRetry = error.canRetry !== false; + errorData.errorType = 'jre'; + } // Handle Butler-specific errors - if (error.butlerError) { + else if (error.butlerError) { console.log('[Main] Processing Butler error with retry context'); errorData.retryData = { branch: error.branch || 'release', fileName: error.fileName || '4.pwr', cacheDir: error.cacheDir }; - errorData.canRetry = error.canRetry !== undefined ? error.canRetry : true; - - // Add Butler-specific error details - if (error.stderr) { - console.error('[Main] Butler stderr:', error.stderr); - } - if (error.stdout) { - console.log('[Main] Butler stdout:', error.stdout); - } - if (error.errorCode) { - console.log('[Main] Butler error code:', error.errorCode); - } + errorData.canRetry = error.canRetry !== false; } // Handle PWR download errors else if (error.branch && error.fileName) { @@ -470,7 +475,7 @@ ipcMain.handle('install-game', async (event, playerName, javaPath, installPath, fileName: error.fileName, cacheDir: error.cacheDir }; - errorData.canRetry = error.canRetry !== undefined ? error.canRetry : true; + errorData.canRetry = error.canRetry !== false; } // Default fallback for other errors else { @@ -479,6 +484,8 @@ ipcMain.handle('install-game', async (event, playerName, javaPath, installPath, branch: 'release', fileName: '4.pwr' }; + // For generic errors, assume it's retryable unless specified + errorData.canRetry = error.canRetry !== false; } // Send enhanced error info for retry UI @@ -636,7 +643,7 @@ ipcMain.handle('uninstall-game', async () => { try { await uninstallGame(); } catch (error) { - console.error('Uninstall error:', error); + // console.error('Uninstall error:', error); return { success: false, error: error.message }; } }); @@ -669,16 +676,7 @@ ipcMain.handle('repair-game', async () => { ipcMain.handle('retry-download', async (event, retryData) => { try { console.log('[IPC] retry-download called with data:', retryData); - - // Handle null retry data gracefully - if (!retryData || !retryData.branch || !retryData.fileName) { - console.log('[IPC] Invalid retry data, using defaults'); - retryData = { - branch: 'release', - fileName: '4.pwr' - }; - } - + const progressCallback = (message, percent, speed, downloaded, total, retryState) => { if (mainWindow && !mainWindow.isDestroyed()) { const data = { @@ -693,14 +691,36 @@ ipcMain.handle('retry-download', async (event, retryData) => { } }; + // Handle JRE download retries + if (retryData && retryData.isJREError) { + console.log(`[IPC] Retrying JRE download: jreUrl=${retryData.jreUrl}, fileName=${retryData.fileName}`); + console.log('[IPC] Full JRE retry data:', JSON.stringify(retryData, null, 2)); + + const { retryJREDownload } = require('./backend/managers/javaManager'); + await retryJREDownload(retryData.jreUrl, jreCacheFile, progressCallback); + const jreCacheFile = path.join(retryData.cacheDir, retryData.fileName); + + return { success: true }; + } + + // Handle PWR download retries (default) + if (!retryData || !retryData.branch || !retryData.fileName) { + console.log('[IPC] Invalid retry data, using PWR defaults'); + retryData = { + branch: 'release', + fileName: '4.pwr' + }; + } + // Extract PWR download info from retryData const branch = retryData.branch; const fileName = retryData.fileName; const cacheDir = retryData.cacheDir; console.log(`[IPC] Retrying PWR download: branch=${branch}, fileName=${fileName}`); + console.log('[IPC] Full PWR retry data:', JSON.stringify(retryData, null, 2)); - // Perform the retry with enhanced context + // Perform retry with enhanced context await retryPWRDownload(branch, fileName, progressCallback, cacheDir); return { success: true }; @@ -710,15 +730,28 @@ ipcMain.handle('retry-download', async (event, retryData) => { // Send error update to frontend with context if (mainWindow && !mainWindow.isDestroyed()) { - const data = { - message: errorMessage, - error: true, - canRetry: true, - retryData: { + const isJreError = retryData?.isJREError; + const errorRetryData = isJreError ? + { + isJREError: true, + jreUrl: retryData?.jreUrl, + fileName: retryData?.fileName, + cacheDir: retryData?.cacheDir, + osName: retryData?.osName, + arch: retryData?.arch + } : + { branch: retryData?.branch || 'release', fileName: retryData?.fileName || '4.pwr', cacheDir: retryData?.cacheDir - } + }; + + const data = { + message: errorMessage, + error: true, + canRetry: error.canRetry !== false, // Respect canRetry from the thrown error + retryData: errorRetryData, + errorType: isJreError ? 'jre' : 'general' // Add errorType for the UI }; mainWindow.webContents.send('progress-update', data); } @@ -846,7 +879,6 @@ ipcMain.handle('load-settings', async () => { }); const { getModsPath, loadInstalledMods, downloadMod, uninstallMod, toggleMod, getCurrentUuid, getAllUuidMappings, setUuidForUser, generateNewUuid, deleteUuidForUser, resetCurrentUserUuid } = require('./backend/launcher'); -const { retryPWRDownload } = require('./backend/managers/gameManager'); const os = require('os'); ipcMain.handle('get-local-app-data', async () => { From f974d9c767bb0cf7ae9e08146c14c9e15be6c225 Mon Sep 17 00:00:00 2001 From: AMIAY Date: Sat, 24 Jan 2026 22:33:18 +0100 Subject: [PATCH 6/9] Update package-lock.json --- package-lock.json | 156 ++++++++++++++-------------------------------- 1 file changed, 47 insertions(+), 109 deletions(-) diff --git a/package-lock.json b/package-lock.json index 346839d..f7ad214 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "discord-rpc": "^4.0.1", "dotenv": "^17.2.3", "electron-updater": "^6.7.3", + "fs-extra": "^11.3.3", "tar": "^6.2.1", "uuid": "^9.0.1" }, @@ -147,6 +148,21 @@ "global-agent": "^3.0.0" } }, + "node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, "node_modules/@electron/notarize": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", @@ -345,34 +361,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/@electron/universal/node_modules/fs-extra": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", - "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@electron/universal/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/@electron/universal/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -389,16 +377,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@electron/universal/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/@electron/windows-sign": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", @@ -406,7 +384,6 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, - "peer": true, "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", @@ -421,50 +398,6 @@ "node": ">=14.14" } }, - "node_modules/@electron/windows-sign/node_modules/fs-extra": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", - "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@electron/windows-sign/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@electron/windows-sign/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/@isaacs/balanced-match": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", @@ -916,6 +849,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1679,8 +1613,7 @@ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true, "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -1897,6 +1830,7 @@ "integrity": "sha512-ce4Ogns4VMeisIuCSK0C62umG0lFy012jd8LMZ6w/veHUeX4fqfDrGe+HTWALAEwK6JwKP+dhPvizhArSOsFbg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "26.4.0", "builder-util": "26.3.4", @@ -2272,7 +2206,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", @@ -2293,7 +2226,6 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -2310,16 +2242,6 @@ "dev": true, "license": "MIT" }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -2612,18 +2534,38 @@ } }, "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", + "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=14.14" + } + }, + "node_modules/fs-extra/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" } }, "node_modules/fs-minipass": { @@ -3561,7 +3503,6 @@ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -3914,6 +3855,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3943,7 +3885,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "commander": "^9.4.0" }, @@ -3961,7 +3902,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -4158,7 +4098,6 @@ "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -4619,7 +4558,6 @@ "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" From 127c38f98b79d4d2c748488dd3180efcd1567a60 Mon Sep 17 00:00:00 2001 From: xSamiVS Date: Sat, 24 Jan 2026 23:01:42 +0100 Subject: [PATCH 7/9] Update Spanish locale, add missing CurseForge API Key translation, implement Turkish translation, and fix contributor links comma. (#135) * Update Spanish locale and add missing CurseForge API Key translation - Updated the Spanish locale name to distinguish between multiple locale types. - Added missing translation for the page indicating the missing CurseForge API Key. * Implemented Turkish locale support * Add Turkish locale to available languages * Add missing comma in contributor links * Correct Portuguese language name in available languages --------- Co-authored-by: Fazri Gading --- GUI/index.html | 70 ++++---- GUI/js/i18n.js | 5 +- GUI/js/mods.js | 9 +- GUI/locales/en.json | 4 +- GUI/locales/{es.json => es-ES.json} | 10 +- GUI/locales/pt-BR.json | 4 +- GUI/locales/tr-TR.json | 246 ++++++++++++++++++++++++++++ 7 files changed, 306 insertions(+), 42 deletions(-) rename GUI/locales/{es.json => es-ES.json} (96%) create mode 100644 GUI/locales/tr-TR.json diff --git a/GUI/index.html b/GUI/index.html index fa94706..57a883e 100644 --- a/GUI/index.html +++ b/GUI/index.html @@ -602,38 +602,38 @@ - + - +